自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

深入解析Go Channel各狀態(tài)下的操作結(jié)果

網(wǎng)絡(luò) 網(wǎng)絡(luò)管理
golang中的通道就是用來(lái)在協(xié)程間進(jìn)行通信的。我們從源碼級(jí)別推導(dǎo)了針對(duì)通道的各個(gè)狀態(tài)下的操作所產(chǎn)生的結(jié)果。

大家好,我是漁夫子。

channel是golang中獨(dú)有的特性,也是面試中經(jīng)常被問(wèn)到的。相信大家都看到過(guò)下面這張圖,對(duì)于不同狀態(tài)下通道,在操作時(shí)會(huì)有什么結(jié)果。

圖片

這張圖總結(jié)的非常好。但我們不能死記硬背這些結(jié)果。要了解其底層的基本原理,就能理解這些結(jié)果是怎么來(lái)的。

我們分三部分來(lái)講。先是channel的基礎(chǔ)使用,基礎(chǔ)使用提現(xiàn)了channel有哪些特性。再引出channel的底層數(shù)據(jù)結(jié)構(gòu)。底層數(shù)據(jù)結(jié)構(gòu)就是圍繞這些特性而建立的。最后再看go是如何基于底層數(shù)據(jù)結(jié)構(gòu)來(lái)實(shí)現(xiàn)這些特性的。

channel的基礎(chǔ)使用

通道的定義和初始化

通過(guò)var定義通道

通過(guò)var定義一個(gè)通道變量ch,這個(gè)變量能夠接收整型的數(shù)據(jù)。當(dāng)然也可以指定其他任何數(shù)據(jù)類型。

var ch chan int
  • ch 代表變量名
  • chan固定值。代表ch是通道類型
  • int代表在通道ch中存儲(chǔ)的是整型數(shù)據(jù)。
  • ch變量的默認(rèn)值是nil。對(duì)于nil通道在操作時(shí)會(huì)有特殊的場(chǎng)景,一會(huì)我們也會(huì)講解。

通過(guò)make初始化通道

通過(guò)make可以初始化無(wú)緩沖區(qū)通道和緩沖區(qū)通道。區(qū)別就在于make中是否指定了緩沖區(qū)的大小。如下:

var ch = make(chan int) //初始化無(wú)緩沖通道

var ch = make(chan int, 10) //緩沖區(qū)通道,緩沖區(qū)可以存10個(gè)元素

無(wú)緩沖通道和有緩沖通道的區(qū)別可以從屬性上和行為兩方面來(lái)體現(xiàn):

  • 從屬性上區(qū)別:通道是否有一段緩沖區(qū)來(lái)暫存元素。
  • 從行為上區(qū)別:發(fā)送者和接收者是否同步的還是異步的。
  • 從底層數(shù)據(jù)結(jié)構(gòu)上區(qū)別:是否有一塊緩沖區(qū)來(lái)暫存數(shù)據(jù)。這個(gè)后面會(huì)詳細(xì)講解。

通道的操作

golang中對(duì)于通道有三種操作:往通道中發(fā)送元素、從通道中接收元素、關(guān)閉通道。如下:往通道中發(fā)送元素:

var ch chan int = make(chan int, 10)

2 ->ch //發(fā)送元素

var item int
item <-ch //接收元素

close(ch) //關(guān)閉元素

總結(jié)一下:

  • 通道有三種操作:發(fā)送、接收和關(guān)閉。
  • 通道有三種類型:nil通道、無(wú)緩沖通道和有緩沖通道。
  • 通道有2種狀態(tài):關(guān)閉狀態(tài)和未關(guān)閉狀態(tài)。
  • 緩沖通道的未關(guān)閉狀態(tài)又可以分為緩沖區(qū)滿、緩沖區(qū)未滿狀態(tài)。

那么,通道是基于怎樣的數(shù)據(jù)結(jié)構(gòu)來(lái)完成這些行為的呢?

channel的數(shù)據(jù)結(jié)構(gòu)

我們先給出channel的底層數(shù)據(jù)結(jié)構(gòu),如下:

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
}

根據(jù)上面的結(jié)構(gòu)定義,依次解釋下各個(gè)字段的含義:

  • buf:指向一個(gè)數(shù)組,代表的是一個(gè)隊(duì)列,結(jié)合sendx和recvx字段實(shí)現(xiàn)了環(huán)形隊(duì)列。緩存對(duì)應(yīng)的元素。緩沖區(qū)通道就是利用這個(gè)字段實(shí)現(xiàn)的。
  • qcount:在buf隊(duì)列中當(dāng)前有多少個(gè)元素。
  • dataqsiz:代表隊(duì)列buf的容量。在使用make進(jìn)行初始化時(shí),指定的元素個(gè)數(shù)就存在該字段中。
  • elemsize:一個(gè)元素的字節(jié)大小。根據(jù)該元素的大小,可以初始化buf的容量的大小。通過(guò)elemsize*容量就能知道該給buf分配多少字節(jié)的空間了。
  • closed:代表該通道是否被關(guān)閉。其值只有0和1。1代表該通道已經(jīng)關(guān)閉了。0代表未關(guān)閉。
  • elemtype:代表元素的類型。
  • sendx:代表的是發(fā)送下一個(gè)元素應(yīng)該存儲(chǔ)的位置
  • recvx:代表的是下一個(gè)接收元素的位置。
  • recvq:代表的是等待接收元素的協(xié)程隊(duì)列
  • sendq:代表的是發(fā)送元素的協(xié)程隊(duì)列。

根據(jù)以上結(jié)果,繪制成圖會(huì)容易理解點(diǎn),如下:

圖片

緩沖通道和非緩沖通道的區(qū)別

從定義上,緩沖通道和非緩沖通道都是通過(guò)make來(lái)初始化的。不同點(diǎn)在于是否在make函數(shù)上指定了通道的容量大小。如下:

unbufferCh := make(chan int) //初始化非緩沖區(qū)通道

bufferCh := make(chan int, 10) //初始化一個(gè)能緩沖10個(gè)元素的通道

從通道的底層數(shù)據(jù)結(jié)構(gòu)上來(lái)說(shuō),非緩沖渠道不會(huì)初始化結(jié)構(gòu)體中的buf字段。而緩沖渠道則會(huì)初始化buf字段。該字段指向一塊內(nèi)存區(qū)域。如下圖:

圖片

通道的發(fā)送、接收流程

通過(guò)源碼我們梳理出來(lái)了給通道發(fā)送數(shù)據(jù)和從通道中接收數(shù)據(jù)的流程圖。這張流程圖將緩沖通道和無(wú)緩沖通道兩種狀態(tài)下的發(fā)送和接收流程都包含了,所以看起來(lái)會(huì)比較復(fù)雜。但是沒(méi)關(guān)系,下面我們會(huì)分解這張圖。

圖片

通過(guò)上面的流程,大家需要注意的一點(diǎn)就是,無(wú)論是在發(fā)送還是接收操作時(shí),都是優(yōu)先從等待隊(duì)列中獲取對(duì)應(yīng)的線程,如果有,則直接接收或發(fā)送;如果等待隊(duì)列沒(méi)有協(xié)程,然后再看是否有緩沖區(qū)。這一點(diǎn)需要大家額外注意。

圖片

各狀態(tài)通道的操作

無(wú)緩沖通道

根據(jù)上述無(wú)緩沖通道其實(shí)本質(zhì)上就是沒(méi)有緩沖區(qū)。在初始化時(shí)不指定make的容量即可。實(shí)際上這也叫做同步發(fā)送和接收。針對(duì)這種狀態(tài)的通道,當(dāng)發(fā)送數(shù)據(jù)時(shí),如果接收隊(duì)列中有等待的接收協(xié)程,那么就能發(fā)送成功;否則,進(jìn)入阻塞狀態(tài)。反之,亦然。其流程圖就是圖中的紅色箭頭部分,如下:

圖片

再簡(jiǎn)化一下就是:

  • 往無(wú)緩沖區(qū)中發(fā)送數(shù)據(jù)時(shí),如果有等待接收的協(xié)程,則發(fā)送成功;否則,發(fā)送協(xié)程進(jìn)入阻塞狀態(tài)。
  • 從無(wú)緩沖區(qū)接收數(shù)據(jù)時(shí),如果有等待發(fā)送的協(xié)程,則接收成功;否則,接收協(xié)程進(jìn)入阻塞狀態(tài)。

那么,上面的圖可以簡(jiǎn)化成如下:

圖片

另外需要額外注意一點(diǎn),對(duì)于非緩沖區(qū)通道的發(fā)送和接收操作。如果是在main函數(shù)中進(jìn)行發(fā)送和接收,那么會(huì)造成死鎖。如下:

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")
}

圖片

所以,對(duì)于非緩沖區(qū)通道的發(fā)送和接收操作,最主要的問(wèn)題就是可能會(huì)造成阻塞。除非,兩個(gè)發(fā)送和接收協(xié)程都存在,而且要在不同的協(xié)程里。

有緩沖通道

有緩沖區(qū)通道就是在通道中有一塊緩沖區(qū),發(fā)送和接收都可以針對(duì)緩沖區(qū)進(jìn)行操作。也稱為異步發(fā)送和接收。在有緩沖通道的狀態(tài)下,j對(duì)于發(fā)送操作來(lái)說(shuō),有緩沖通道的狀態(tài)分為緩沖區(qū)滿和未滿兩種狀態(tài)。根據(jù)上面的發(fā)送流程圖來(lái)說(shuō),當(dāng)緩沖區(qū)滿了,自然就不能再發(fā)送了,就會(huì)進(jìn)入等待發(fā)送隊(duì)列。同時(shí)阻塞,等待被接收協(xié)程喚醒。

對(duì)于接收操作來(lái)說(shuō),有緩沖通道的狀態(tài)分為緩沖區(qū)空和未滿兩種狀態(tài)。同樣,如果當(dāng)緩沖區(qū)空時(shí),無(wú)數(shù)據(jù)可接收,自然就進(jìn)入到接收等待隊(duì)列。同時(shí)進(jìn)入阻塞,等待被發(fā)送協(xié)程喚醒。

圖片

已關(guān)閉狀態(tài)的通道

關(guān)閉通道是通過(guò)**close**函數(shù)進(jìn)行的。本質(zhì)上關(guān)閉一個(gè)通道,就是將通道結(jié)構(gòu)中的closed字段置為 1。從源代碼中可以獲知:

  • 關(guān)閉nil通道:panic
  • 關(guān)閉已經(jīng)關(guān)閉了的通道:panic。這一點(diǎn)可以這樣理解,關(guān)閉一個(gè)已經(jīng)關(guān)閉的通道是沒(méi)有任何意義的。

圖片

發(fā)送消息到已關(guān)閉的通道

給已經(jīng)關(guān)閉了的通道發(fā)送消息會(huì)引發(fā)panic。這個(gè)很好理解,因?yàn)橥ǖ酪呀?jīng)關(guān)閉,就是為了不讓發(fā)消息了。如下代碼:

圖片

從已關(guān)閉的通道接收消息

從已關(guān)閉的通道中接收消息時(shí),都能操作成功。但會(huì)根據(jù)通道中是否有元素有以下不同:

  • 如果通道中已經(jīng)沒(méi)有元素了,則會(huì)返回一個(gè)false的狀態(tài)。
  • 如果通道中有元素,則會(huì)繼續(xù)接收通道中的元素,直到接收完,并返回false。

圖片

你看,其實(shí)代碼也很簡(jiǎn)單。我們將代碼拆解一下,就是右側(cè)的流程圖。

nil通道

通過(guò)以下方式定義的通道類型的變量,其默認(rèn)值就是nil。

var ch chan int

nil通道相當(dāng)于沒(méi)有分配通道的底層結(jié)構(gòu)

如下是從源代碼中截取的各個(gè)操作以及對(duì)應(yīng)操作結(jié)果。通過(guò)源代碼可獲知:

  • 關(guān)閉nil通道會(huì)panic
  • 從nil通道接收、發(fā)送消都會(huì)阻塞

圖片

總結(jié)

golang中的通道就是用來(lái)在協(xié)程間進(jìn)行通信的。我們從源碼級(jí)別推導(dǎo)了針對(duì)通道的各個(gè)狀態(tài)下的操作所產(chǎn)生的結(jié)果。最后總結(jié)一下:緩沖區(qū)通道:

  • 只要有緩沖空間就能發(fā)送成功。除非緩沖空間滿了,則產(chǎn)生阻塞。
  • 只要緩沖空間中有元素就能接收成功。除非沒(méi)有元素,則產(chǎn)生阻塞。

nil通道:

  • nil通道是沒(méi)有初始化底層數(shù)據(jù)結(jié)構(gòu)的通道。因?yàn)闆](méi)有空間可存儲(chǔ)任何元素,所以發(fā)送和接收都會(huì)產(chǎn)生阻塞。關(guān)閉nil通道,則會(huì)引發(fā)panic。

已關(guān)閉的通道:

  • 往已關(guān)閉的通道中發(fā)送消息,會(huì)引發(fā)panic。
  • 從已關(guān)閉通道中接收消息,會(huì)成功。
  • 關(guān)閉已關(guān)閉的通道,也會(huì)引發(fā)panic。
責(zé)任編輯:武曉燕 來(lái)源: Go學(xué)堂
相關(guān)推薦

2010-06-09 17:01:48

路由選擇協(xié)議

2017-02-10 21:15:22

Windows 10Windows操作技巧

2010-08-23 10:27:14

恢復(fù)IOS

2011-09-08 18:28:03

windowsXP鎖定狀態(tài)

2021-11-18 09:20:29

Channel語(yǔ)言代碼

2017-08-31 11:28:47

Slice底層實(shí)現(xiàn)

2009-12-29 09:11:54

Windows 7系統(tǒng)鎖定

2022-04-06 21:29:44

邊緣計(jì)算數(shù)據(jù)存儲(chǔ)數(shù)據(jù)中心

2021-01-15 08:37:28

JAR加載

2024-06-19 10:08:34

GoChannel工具

2014-06-11 13:22:44

2024-09-06 10:48:13

2024-07-30 12:24:23

2021-09-30 09:21:28

Go語(yǔ)言并發(fā)編程

2025-04-02 05:23:00

GoChannel數(shù)據(jù)

2021-07-02 06:54:45

GoJavachannel

2022-03-04 10:07:45

Go語(yǔ)言字節(jié)池

2024-09-02 09:00:59

2023-03-09 09:06:13

ChanneGo開發(fā)

2023-07-27 13:46:10

go開源項(xiàng)目
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)