天天看點

Go語言基礎:并發

并行與并發

理論式的概念:

并行:多件事在同一時刻發生。

并發:多件事在同一時間間隔發生。

5歲小孩都能看懂的解釋:

Go語言基礎:并發

摘自:http://www.cnblogs.com/yangecnu/p/3164167.html 和 Concurrent and Parallel Programming

上文如果用程式員的語言來講,CPU處理器相當于上圖的咖啡機的角色,任務相當于隊列中的人。

并發與并行的差別:

一定要仔細閱讀此文:http://blog.csdn.net/coolmeme/article/details/9997609 。這篇文章提到了網絡伺服器并發連接配接數、吐吞量、寬帶的概念,對于初學者應該很受用。

Goruntine

goruntine原理

我們知道Go從語言層面就支援了并發,而goruntine是go并發設計的核心。goruntine說到底是協程【Go Web 程式設計裡是線程,也是對的,因為協程類似于使用者态線程】。具體原理實作參考:

1. 以goroutine為例看協程的相關概念

2. goroutine與排程器

3. 廖雪峰:協程

4. 知乎:協程的好處是什麼?

5. 知乎:golang的goroutine是如何實作的?

這些參考文章建議讀者好好看看。

了解了協程、goruntine的實作機制,接下來學習如何啟動goruntine。

啟動goruntine

goroutine 通過關鍵字 go 就啟動了一個 goroutine。

例子:

package main

import (
    "fmt"
    "runtime"
)

func say(s string) {
    for i :=; i <; i++ {
        runtime.Gosched() //表示讓cpu将控制權給其他人
        fmt.Println(s)
    }
}

func main() {
    runtime.GOMAXPROCS)
    go say("world")
    say("hello")
}
           

輸出:

hello
world
hello
world
hello
world
hello
world
hello
           

很簡單,在函數前加一個go關鍵詞就啟動了goruntine。

Channel

channel是什麼

channel是一種通信通道,goruntine之間的資料通信通過channel來實作。goruntine通過channel發送或者接收消息。

channel的基本操作文法:

cl := make(chan int) //建立一個無緩沖的int型channel,可以根據需求建立bool、string等類型的channel
c1 := make(chan int,) //建立有緩沖的int型channel
cl <- x //發送x到channel cl
x := <- cl //從cl中接收資料,并指派給x
           

無緩沖的例子:

package main

import (
    "fmt"
    "time"
)

func sendChan(cl chan string) {
    fmt.Println("[send_start]")
    cl <- "hello world" // 向cl中加資料,如果沒有其他goroutine來取走這個資料,那麼挂起sendChan, 直到getChan函數把"hello world"這個資料拿走
    fmt.Println("[send_end]")
}

func getChan(cl chan string) {
    fmt.Println("[get_start]")
    s := <-cl // 從cl取資料,如果cl中還沒放資料,那就挂起getChan線程,直到sendChan函數中放資料為止
    fmt.Println("[get_end]" + s)
}

func main() {
    cl := make(chan string)

    go sendChan(cl)
    go getChan(cl)

    time.Sleep(time.Second)
}
           

輸出:

[send_start]
[get_start]
[get_end]hello world
[send_end]
           

上面的例子存在3個goruntine,注意main也在一個goruntine中。如果函數main中沒有 time.Sleep(time.Second),你會發現什麼輸出都不會有,為什麼呢?是因為另外兩個goruntine還沒來得及跑,主函數main就已經退出了。

是以需要讓main等一下,time.Sleep(time.Second)就是讓main停頓一秒再輸出。

無緩沖的channel的接收和發送都是阻塞的,也就是說:

  • 資料流入無緩沖信道, 如果沒有其他goroutine來拿走這個資料,那麼目前線阻塞
  • 從無緩沖信道取資料,必須要有資料流進來才可以,否則目前goroutine阻塞

有緩沖的例子:

package main

import (
    "fmt"
    "time"
)

func sendChan(cl chan int, len int) {
    fmt.Println("sendChan_enter")
    for i :=; i < len; i++ {
        fmt.Println("# ", i)
        cl <- i //cl的存儲第4個資料的時候,會阻塞目前goruntine,直到其它goruntine取走一個或多個資料
    }
    fmt.Println("sendChan_end")
}

func getChan(cl chan int, len int) {
    fmt.Println("getChan_enter")
    for i :=; i < len; i++ {
        data := <-cl
        fmt.Println("$ ", data)//當cl的資料為空時,阻塞目前goruntine,直到新的資料寫入cl
    }
    fmt.Println("getChan_end")
}

func main() {
    cl := make(chan int,)// 寫入3個元素都不會阻塞目前goroutine, 存儲個數達到4的時候會阻塞

    go sendChan(cl,)
    go getChan(cl,) 

    time.Sleep(time.Second)
}
           

輸出:

sendChan_enter
#  0
#  1
#  2
#  3
getChan_enter
$  
$  
$  
$  
#  4
#  5
#  6
#  7
#  8
$  
getChan_end
           

為什麼sendChan_end沒有輸出?

getChan取完5個資料後,getChan這個goruntine就會挂起,而sendChan線程因為資料填滿,無法将剩餘的資料寫入chanl而挂起,最後因main所在的goruntine逾時1秒結束而結束。故而看不到sendChan_end的輸出。

  • 有緩沖的channel是可以無阻塞的寫入,當緩沖填滿時,再次寫入新的資料時,目前goruntine會發生阻塞,直到其它goruntine從channel中取走一些資料:
  • 有緩沖的channel可以無阻塞的擷取資料,當資料取空時,再次取新的資料時,目前的goruntine會發生阻塞,直到其它goruntine往channel寫入新的資料

close

生産者【發送channel的goruntine】通過關鍵字 close 函數關閉 channel。關閉 channel 之後就無法再發送任何資料了, 在消費方【接收channel的goruntine】可以通過文法 v, ok := <-ch 測試 channel 是否被關閉。如果 ok 傳回 false,那麼說明 channel 已經沒有任何資料并且已經被關閉。

不過一般用得少,網上關于它的描述也不多。

select

文法結構類似于switch。

select {
    case cl<-x:
        go語句
    case <-cl:
        go語句
    default: //可選,
        go語句
}
           
  • 每個case隻能是channel的擷取或者寫入,不能是其它語句。
  • 當每個case都無法執行,如果有default,執行default;如果沒有default,目前goruntine阻塞。
  • 當多個case都可以執行的時候,随機選出一個執行。

關于select的用法,強烈推薦閱讀:【GOLANG】Go語言學習-select用法