天天看點

使用golang的channel的坑

很多時候我們經過使用有緩沖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&lt; </code><code>10</code><code>;i++ {</code>

<code>            </code><code>cache&lt;-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 &lt;-chan </code><code>int</code><code>)  {    </code><code>for</code>  <code>{        select {        </code><code>case</code> <code>i:=&lt;-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 &lt; </code><code>10000000</code><code>; i++ {</code>

<code>        </code><code>bf &lt;- 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 := &lt;-bf:</code>

<code>                </code><code>sprint(str)         </code><code>case</code> <code>&lt;-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&lt;</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 := &lt;-bf:</code>

<code>            </code><code>sprint(str)        </code><code>case</code> <code>&lt;-time.After(</code><code>3</code> <code>* time.Second):</code>

<code>            </code><code>wg.Done()</code>

版權聲明:原創作品,如需轉載,請注明出處。否則将追究法律責任

本文轉自 夢朝思夕 51CTO部落格,原文連結:http://blog.51cto.com/qiangmzsx/1966399