大家好,我是漁夫子。
(資料圖片)
channel是golang中獨有的特性,也是面試中經常被問到的。相信大家都看到過下面這張圖,對于不同狀態下通道,在操作時會有什么結果。
這張圖總結的非常好。但我們不能死記硬背這些結果。要了解其底層的基本原理,就能理解這些結果是怎么來的。
我們分三部分來講。先是channel的基礎使用,基礎使用提現了channel有哪些特性。再引出channel的底層數據結構。底層數據結構就是圍繞這些特性而建立的。最后再看go是如何基于底層數據結構來實現這些特性的。
channel的基礎使用通道的定義和初始化通過var定義通道通過var定義一個通道變量ch,這個變量能夠接收整型的數據。當然也可以指定其他任何數據類型。
var ch chan int
ch 代表變量名chan固定值。代表ch是通道類型int代表在通道ch中存儲的是整型數據。ch變量的默認值是nil。對于nil通道在操作時會有特殊的場景,一會我們也會講解。通過make初始化通道
通過make可以初始化無緩沖區通道和緩沖區通道。區別就在于make中是否指定了緩沖區的大小。如下:
var ch = make(chan int) //初始化無緩沖通道var ch = make(chan int, 10) //緩沖區通道,緩沖區可以存10個元素
無緩沖通道和有緩沖通道的區別可以從屬性上和行為兩方面來體現:
從屬性上區別:通道是否有一段緩沖區來暫存元素。從行為上區別:發送者和接收者是否同步的還是異步的。從底層數據結構上區別:是否有一塊緩沖區來暫存數據。這個后面會詳細講解。通道的操作golang中對于通道有三種操作:往通道中發送元素、從通道中接收元素、關閉通道。如下:往通道中發送元素:
var ch chan int = make(chan int, 10)2 ->ch //發送元素var item intitem <-ch //接收元素close(ch) //關閉元素
總結一下:
通道有三種操作:發送、接收和關閉。通道有三種類型:nil通道、無緩沖通道和有緩沖通道。通道有2種狀態:關閉狀態和未關閉狀態。緩沖通道的未關閉狀態又可以分為緩沖區滿、緩沖區未滿狀態。那么,通道是基于怎樣的數據結構來完成這些行為的呢?
channel的數據結構我們先給出channel的底層數據結構,如下:
type hchan struct { qcount uint // total data in the queue dataqsiz uint // size of the circular queue buf unsafe.Pointer // points to an array of dataqsiz elements elemsize uint16 closed uint32 elemtype *_type // element type sendx uint // send index recvx uint // receive index recvq waitq // list of recv waiters sendq waitq // list of send waiters // lock protects all fields in hchan, as well as several // fields in sudogs blocked on this channel. // // Do not change another G"s status while holding this lock // (in particular, do not ready a G), as this can deadlock // with stack shrinking. lock mutex}type waitq struct { first *sudog last *sudog}
根據上面的結構定義,依次解釋下各個字段的含義:
buf:指向一個數組,代表的是一個隊列,結合sendx和recvx字段實現了環形隊列。緩存對應的元素。緩沖區通道就是利用這個字段實現的。qcount:在buf隊列中當前有多少個元素。dataqsiz:代表隊列buf的容量。在使用make進行初始化時,指定的元素個數就存在該字段中。elemsize:一個元素的字節大小。根據該元素的大小,可以初始化buf的容量的大小。通過elemsize*容量就能知道該給buf分配多少字節的空間了。closed:代表該通道是否被關閉。其值只有0和1。1代表該通道已經關閉了。0代表未關閉。elemtype:代表元素的類型。sendx:代表的是發送下一個元素應該存儲的位置recvx:代表的是下一個接收元素的位置。recvq:代表的是等待接收元素的協程隊列sendq:代表的是發送元素的協程隊列。根據以上結果,繪制成圖會容易理解點,如下:
緩沖通道和非緩沖通道的區別從定義上,緩沖通道和非緩沖通道都是通過make來初始化的。不同點在于是否在make函數上指定了通道的容量大小。如下:
unbufferCh := make(chan int) //初始化非緩沖區通道bufferCh := make(chan int, 10) //初始化一個能緩沖10個元素的通道
從通道的底層數據結構上來說,非緩沖渠道不會初始化結構體中的buf字段。而緩沖渠道則會初始化buf字段。該字段指向一塊內存區域。如下圖:
通道的發送、接收流程通過源碼我們梳理出來了給通道發送數據和從通道中接收數據的流程圖。這張流程圖將緩沖通道和無緩沖通道兩種狀態下的發送和接收流程都包含了,所以看起來會比較復雜。但是沒關系,下面我們會分解這張圖。
通過上面的流程,大家需要注意的一點就是,無論是在發送還是接收操作時,都是優先從等待隊列中獲取對應的線程,如果有,則直接接收或發送;如果等待隊列沒有協程,然后再看是否有緩沖區。這一點需要大家額外注意。
各狀態通道的操作無緩沖通道根據上述無緩沖通道其實本質上就是沒有緩沖區。在初始化時不指定make的容量即可。實際上這也叫做同步發送和接收。針對這種狀態的通道,當發送數據時,如果接收隊列中有等待的接收協程,那么就能發送成功;否則,進入阻塞狀態。反之,亦然。其流程圖就是圖中的紅色箭頭部分,如下:
再簡化一下就是:
往無緩沖區中發送數據時,如果有等待接收的協程,則發送成功;否則,發送協程進入阻塞狀態。從無緩沖區接收數據時,如果有等待發送的協程,則接收成功;否則,接收協程進入阻塞狀態。那么,上面的圖可以簡化成如下:
另外需要額外注意一點,對于非緩沖區通道的發送和接收操作。如果是在main函數中進行發送和接收,那么會造成死鎖。如下:
func main() { var ch = make(chan int) <-ch fmt.Println("the End")}//或func main() { var ch = make(chan int) ch <- 2 fmt.Println("the End")}
所以,對于非緩沖區通道的發送和接收操作,最主要的問題就是可能會造成阻塞。除非,兩個發送和接收協程都存在,而且要在不同的協程里。
有緩沖通道有緩沖區通道就是在通道中有一塊緩沖區,發送和接收都可以針對緩沖區進行操作。也稱為異步發送和接收。在有緩沖通道的狀態下,j對于發送操作來說,有緩沖通道的狀態分為緩沖區滿和未滿兩種狀態。根據上面的發送流程圖來說,當緩沖區滿了,自然就不能再發送了,就會進入等待發送隊列。同時阻塞,等待被接收協程喚醒。
對于接收操作來說,有緩沖通道的狀態分為緩沖區空和未滿兩種狀態。同樣,如果當緩沖區空時,無數據可接收,自然就進入到接收等待隊列。同時進入阻塞,等待被發送協程喚醒。
已關閉狀態的通道關閉通道是通過**close**函數進行的。本質上關閉一個通道,就是將通道結構中的closed字段置為 1。從源代碼中可以獲知:
關閉nil通道:panic關閉已經關閉了的通道:panic。這一點可以這樣理解,關閉一個已經關閉的通道是沒有任何意義的。發送消息到已關閉的通道給已經關閉了的通道發送消息會引發panic。這個很好理解,因為通道已經關閉,就是為了不讓發消息了。如下代碼:
從已關閉的通道接收消息從已關閉的通道中接收消息時,都能操作成功。但會根據通道中是否有元素有以下不同:
如果通道中已經沒有元素了,則會返回一個false的狀態。如果通道中有元素,則會繼續接收通道中的元素,直到接收完,并返回false。你看,其實代碼也很簡單。我們將代碼拆解一下,就是右側的流程圖。
nil通道通過以下方式定義的通道類型的變量,其默認值就是nil。
var ch chan int
nil通道相當于沒有分配通道的底層結構
如下是從源代碼中截取的各個操作以及對應操作結果。通過源代碼可獲知:
關閉nil通道會panic從nil通道接收、發送消都會阻塞總結golang中的通道就是用來在協程間進行通信的。我們從源碼級別推導了針對通道的各個狀態下的操作所產生的結果。最后總結一下:緩沖區通道:
只要有緩沖空間就能發送成功。除非緩沖空間滿了,則產生阻塞。只要緩沖空間中有元素就能接收成功。除非沒有元素,則產生阻塞。nil通道:
nil通道是沒有初始化底層數據結構的通道。因為沒有空間可存儲任何元素,所以發送和接收都會產生阻塞。關閉nil通道,則會引發panic。已關閉的通道:
往已關閉的通道中發送消息,會引發panic。從已關閉通道中接收消息,會成功。關閉已關閉的通道,也會引發panic。關鍵詞:
下一篇:最后一頁
X 關閉
X 關閉
- 15G資費不大降!三大運營商誰提供的5G網速最快?中國信通院給出答案
- 2聯想拯救者Y70發布最新預告:售價2970元起 迄今最便宜的驍龍8+旗艦
- 3亞馬遜開始大規模推廣掌紋支付技術 顧客可使用“揮手付”結賬
- 4現代和起亞上半年出口20萬輛新能源汽車同比增長30.6%
- 5如何讓居民5分鐘使用到各種設施?沙特“線性城市”來了
- 6AMD實現連續8個季度的增長 季度營收首次突破60億美元利潤更是翻倍
- 7轉轉集團發布2022年二季度手機行情報告:二手市場“飄香”
- 8充電寶100Wh等于多少毫安?鐵路旅客禁止、限制攜帶和托運物品目錄
- 9好消息!京東與騰訊續簽三年戰略合作協議 加強技術創新與供應鏈服務
- 10名創優品擬通過香港IPO全球發售4100萬股 全球發售所得款項有什么用處?