go channel實現

來源:互聯網
上載者:User
這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。

Go語言經過多年的發展,於最近推出了第一個穩定版本。相對於C/C++來說,Go有很多獨特之出,比如提供了相當抽象的工具,如channel和goroutine。本文主要介紹channel的實現方式。

簡介

channel有四個操作:

  • 建立:c = make(chan int)
  • 發送:c <- 1
  • 提取:i <- c
  • 關閉:close(c)

根據建立方式的不同,channel還可分為有buffer的channel和沒有buffer的channel。buffer的大小由make的第二個參數指定,預設為0,即沒有buffer。建立有buffer的channel的方式是:c = make(chan int, 10)

channel的實現主要在檔案src/pkg/runtime/chan.c裡面。它的資料結構如下:

structHchan{uint32qcount;// total data in the quint32dataqsiz;// size of the circular quint16elemsize;boolclosed;uint8elemalign;Alg*elemalg;// interface for element typeuint32sendx;// send indexuint32recvx;// receive indexWaitQrecvq;// list of recv waitersWaitQsendq;// list of send waitersLock;};

發送流程

Hchan中的兩個WaitQ(recvqsendq)是兩個隊列,分別儲存等待從該channel提取和發送的goroutine。以向沒有buffer的channel發送為例,

  • 如果向該channel發送資料的goroutine發現recvq不為空白,則從recvq中取出一個goroutine,然後把資料傳給它,發送完成,發送方goroutine可以繼續執行。提取方goroutine則結束block狀態,可以被調度執行。
  • 否則,發送方goroutine被存入sendq隊列,且發送方goroutine進入block狀態,調度演算法選擇其它goroutine執行。

如果channel有buffer,

  • 如果buffer裡有空間,則把資料存入buffer,發送完成;如果recvq隊列裡有等待的goroutine,則取出一個,並將其喚醒,等待調度執行。發送方goroutine繼續執行。
  • 如果buffer已滿,則發送方goroutine被存入sendq隊列,發送方goroutine進入block狀態,調度演算法選擇其它goroutine執行。

如果向已經關閉的channel發送資料,程式會報錯並異常退出。如下面的程式:

package mainfunc main() {c := make(chan int)d := make(chan int)go func() {<-dclose(c)} ()d <- 4c <- 3}

從已經關閉的channel收取資料不會報錯,也不會異常退出,但是我不確定得到什麼樣的值。除此之外,提取和發送的實現基本是相對的,就不再介紹了。

Buffer空間

buffer的空間緊挨著channel,是在建立的channel的時候一起分配的,

c = (Hchan*)runtime·mal(n + hint*elem->size);

其中hint即為buffer的元素個數,會儲存在dataqsiz裡,另外一起管理buffer的還有qcountsendxrecvx,分別表示buffer裡的元素個數,下一次發送操作存放資料的位置,以及下一次提取資料的位置。這個buffer是個circular buffer。

Channel與Select

channel配合select語句,可以實現multiplex的效果,如:

select {case <-c1:case <-c2:}

c1c2哪個channel先有資料到達,哪個case先執行;都沒有資料,就block住;都有資料,以一個公平的方式隨機播放一個case執行。select語句本身沒有增加channel的操作方式,但是它本身的實現也很有趣:

  • 當select被block住,它所在的goroutine將被掛在多個channel的sendq或者recvq上。比如上面的例子中,select所在的goroutine將被掛在c1c2recvq上,如果這時有另外兩個goroutine同時分別向c1c2發送資料,那麼它們將操作同一個goroutine(儘管是不同的channel),這種情況下,要麼加鎖,要麼用原子操作。這就是為什麼dequeue裡要使用runtime·cas的原因,雖然調用dequeue之前上鎖了,但那是給sendq/recvq上鎖,不是給goroutine上鎖。
  • 不同goroutine裡面的select語句可能操作同一組channel,那麼就有上鎖的必要。Go的實現裡每個channel有自己的鎖,所以select就需要上多個鎖,稍有不慎,可能導致死結。Go的實現是用bubble sort把channel的地址(即Hchan*)排序,然後依次上鎖。
  • 最後就是如何?相對公平。

相對公平的另一個說法就是每個channel被選中的機率是相等的。實現如下:

for(i=0; incase; i++)sel->pollorder[i] = i;for(i=1; incase; i++) {o = sel->pollorder[i];j = runtime·fastrand1()%(i+1);sel->pollorder[i] = sel->pollorder[j];sel->pollorder[j] = o;}

每個迭代做的事情就是在前i個元素裡隨機播放一個放在第i個位置上。這個演算法比programming pearls裡面的難理解,因為每個元素可能被移動多次。我們分兩種情況來討論,對於任意一個位置i,最終落在這個位置的元素可能來自i之前(包括i)或者i之後。

如果是來自與i之前(包括i),那麼它在之後就不能被交換出去。所以它留在位置i的機率為(1/i) * i/(i+1) * (i+1)/(i+2) * ... * (n-1)/n = 1/n

如果來自i之後(如位置k),那麼在換到i之後,不能有其後的元素再和i交換,所以機率為(1/k) * k/(k+1) * ... * (n-1)/n = 1/n

由以上兩種情況可知,任何一個元素出現在位置i的機率都是1/n

因此,按照pollorder的順序依次檢查case是否能夠執行,對於每個case來說,是公平的。

原文連結

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.