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