這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
並發
goroutine
goroutine 是 Go 並行設計的核心。goroutine 說到底其實就是線程,但是他比線程更小,十
幾個 goroutine 可能體現在底層就是五六個線程,Go 語言內部幫你實現了這些 goroutine
之間的記憶體共用。執行 goroutine 只需極少的棧記憶體(大概是 4~5KB),當然會根據相應的數
據伸縮。也正因為如此,可同時運行成千上萬個並發任務。 goroutine 比 thread 更易用、更高
效、更輕便。goroutine 是通過 Go 的 runtime 管理的一個線程管理器
package main
import (
"fmt"
"runtime"
)
func say(s string) {
for i := 0; i < 5; i++ {
runtime.Gosched()
fmt.Println(s)
}
}
func main() {
go say("world") //開一個新的 Goroutines 執行
say("hello") //當前 Goroutines 執行
}
// hello world hello world ^^^^^^^
很容易實現了並發
Channel
goroutine 運行在相同的地址空間,因此訪問共用記憶體必須做好同步。那麼 goroutine 之間如
何進行資料的通訊呢,Go 提供了一個很好的通訊機制 channel。 channel 可以與 Unix shell
中的雙向管道做類比:可以通過它發送或者接收值。這些值只能是特定的類型:channel 類
型。定義一個 channel 時,也需要定義發送到 channel 的值的類型。注意,必須使用 make
建立 channel:
ci := make(chan int)
cs := make(chan string)
cf := make(chan interface{})
channel 通過操作符<-來接收和發送資料
ch <- v // 發送 v 到 channel ch.
v := <-ch // 從 ch 中接收資料,並賦值給 v
寫個小例子:
func sum(a []int, c chan int) {
sum := 0
for _, v := range a {
sum += v
}
c <- sum // send sum to c
}
func main() {
a := []int{7, 2, 8, -9, 4, 0}
c := make(chan int)
go sum(a[:len(a)/2], c)
go sum(a[len(a)/2:], c)
x, y := <-c, <-c // receive from c
fmt.Println(x, y, x + y)
}
Buffered Channels
上面我們介紹了預設的非緩衝類型的 channel,不過 Go 也允許指定 channel 的緩衝大小,
很簡單,就是 channel 可以儲存多少元素。ch:= make(chan bool, 4),建立了可以儲存 4 個
元素的 bool 型 channel。在這個 channel 中,前 4 個元素可以無阻塞的寫入。 當寫入第 5 個
元素時,代碼將會阻塞,直到其他 goroutine 從 channel 中讀取一些元素,騰出空間。
ch := make(chan type, value)
value == 0 ! 無緩衝(阻塞)
value > 0 ! 緩衝(非阻塞,直到 value 個元素)
c := make(chan int, 2)//修改 2 為 1 就報錯,修改 2 為 3 可以正常運行
c <- 1
c <- 2
fmt.Println(<-c)
fmt.Println(<-c)
Range 和 Close
func fibonacci(n int, c chan int) {
x, y := 1, 1
for i := 0; i < n; i++ {
c <- x
x, y = y, x + y
}
close(c)//關閉chan,之後就無法發送資料了
}
func main() {
c := make(chan int, 10)
go fibonacci(cap(c), c)//cap,chan的緩衝大小
for i := range c {//能夠不斷從chan裡讀取資料,知道chan關閉
fmt.Println(i)
}
}
記住應該在生產者的地方關閉 channel,而不是消費的地方去關閉它,這樣容易引起 panic
另外記住一點的就是 channel 不像檔案之類的,不需要經常去關閉,只有當你確實沒有任
何發送資料了,或者你想顯式的結束 range 迴圈之類的
selsect
我們上面介紹的都是只有一個 channel 的情況,那麼如果存在多個 channel 的時候,我們
該如何操作呢,Go 裡面提供了一個關鍵字 select,通過 select 可以監聽 channel 上的資料
流動。
select 預設是阻塞的,只有當監聽的 channel 中有發送或接收可以進行時才會運行,當多
個 channel 都準備好的時候,select 是隨機的選擇一個執行的。我們還可以利用select來設定逾時,
因為程式一旦阻塞就一直停在那裡,所以我們需要設定阻塞逾時。
func main() {
c := make(chan int)
o := make(chan bool)
go func() {
for {
select {
case v := <- c:
println(v)
case <- time.After(5 * time.Second):
println("timeout")
o <- true
break
}
}
}()
<- o
}