很多時候我們經過使用有緩衝channel作為通訊控制的功能,以至有一些誤解和坑出現。
誤解一:有緩衝channel是順序的
執行下面代碼。
package mainimport ( "time" "math/rand")func main(){ cache:=make(chan int,4) go func() { for i:=0;i< 10;i++ { cache<-i } }() go getCache(cache) go getCache(cache) go getCache(cache) time.Sleep(3*time.Second)}func getCache(cache <-chan int) { for { select { case i:=<-cache: println(i) time.Sleep(time.Duration(rand.Int31n(100))*time.Millisecond) } }}
多執行幾次看看結果,並不是每一次都是可以順序輸出的,有緩衝channel是亂序的。因為這裡讓一些同學誤解了,我在此多解釋一下。
針對通道的發送和接收操作都是可能造成相關的goroutine阻塞。試想一下,有多個goroutine向同一個channel發送資料而被阻塞,如果還channel有多餘的緩衝空間時候,最早被阻塞的goroutine會最先被喚醒。也就是說,這裡的喚醒順序與發送操作的開始順序是一致的,對接收操作而言亦為如此。無論是發送還是接收操作,運行時系統每次只會喚醒一個goroutine。 而這裡的亂序是指,如果像使用channel緩衝中多個goroutine實現順序是正確的,因為每一個goroutine搶到處理器的時間點不一致,所以不能保證順序。
誤解二:channel緩衝的大小就是並發度
如下代碼。
package mainimport ("fmt""sync""time")var wg = sync.WaitGroup{}func main() {wg.Add(2)bf := make(chan string, 64)go insert(bf)go get(bf)wg.Wait()}func insert(bf chan string) {str := "CockroachDB 的技術選型比較激進,比如依賴了 HLC 來做事務的時間戳記。但是在 Spanner 的事務模型的 Commit Wait 階段等待時間的選擇,CockroachDB 並沒有辦法做到 10ms 內的延遲;CockroachDB 的 Commit Wait 需要使用者自己指定,但是誰能拍胸脯說 NTP 的時鐘誤差在多少毫秒內?我個人認為在處理跨洲際機房時鐘同步的問題上,基本只有硬體時鐘一種辦法。HLC 是沒辦法解決的。另外 Cockroach 採用了 gossip 來同步節點資訊,當叢集變得比較大的時候,gossip 心跳會是一個非常大的開銷。當然 CockroachDB 的這些技術選擇帶來的優勢就是非常好的易用性,所有邏輯都在一個 binary 中,開箱即用,這個是非常大的優點。"for i := 0; i < 10000000; i++ {bf <- fmt.Sprintf("%s%d", str, i)}wg.Done()}func sprint(s string) {time.Sleep(1000 * time.Millisecond)}func get(bf chan string) {for {go func() {select {case str := <-bf:sprint(str)case <-time.After(3 * time.Second):wg.Done()}}()}}
很多同學乍一看以為定義了
bf := make(chan string, 64)
就是說該程式的並發度控制在了64,執行就會發現記憶體一直在增長。 因為get()函數中啟動的goroutine會越來越多,因為get()每讀取一個資料,insert()就會往channel插入一條資料,此時並發度就不是64了。 需要修改為:
package mainimport ("fmt""sync""time")var wg = sync.WaitGroup{}func main() {wg.Add(2)bf := make(chan string, 64)go insert(bf)//go get(bf) for i:=0;i<64;i++ { go get1(bf) }wg.Wait()}func insert(bf chan string) {str := "CockroachDB 的技術選型比較激進,比如依賴了 HLC 來做事務的時間戳記。但是在 Spanner 的事務模型的 Commit Wait 階段等待時間的選擇,CockroachDB 並沒有辦法做到 10ms 內的延遲;CockroachDB 的 Commit Wait 需要使用者自己指定,但是誰能拍胸脯說 NTP 的時鐘誤差在多少毫秒內?我個人認為在處理跨洲際機房時鐘同步的問題上,基本只有硬體時鐘一種辦法。HLC 是沒辦法解決的。另外 Cockroach 採用了 gossip 來同步節點資訊,當叢集變得比較大的時候,gossip 心跳會是一個非常大的開銷。當然 CockroachDB 的這些技術選擇帶來的優勢就是非常好的易用性,所有邏輯都在一個 binary 中,開箱即用,這個是非常大的優點。"for i := 0; i < 10000000; i++ {bf <- fmt.Sprintf("%s%d", str, i)}wg.Done()}func sprint(s string) {time.Sleep(1000 * time.Millisecond)}func get1(bf chan string) { for { select { case str := <-bf: sprint(str) case <-time.After(3 * time.Second): wg.Done() } }}