成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

深入解析Go Channel各狀態下的操作結果

網絡 網絡管理
golang中的通道就是用來在協程間進行通信的。我們從源碼級別推導了針對通道的各個狀態下的操作所產生的結果。

大家好,我是漁夫子。

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 int
item <-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。
責任編輯:武曉燕 來源: Go學堂
相關推薦

2010-06-09 17:01:48

路由選擇協議

2010-08-23 10:27:14

恢復IOS

2017-02-10 21:15:22

Windows 10Windows操作技巧

2011-09-08 18:28:03

windowsXP鎖定狀態

2017-08-31 11:28:47

Slice底層實現

2021-11-18 09:20:29

Channel語言代碼

2009-12-29 09:11:54

Windows 7系統鎖定

2022-04-06 21:29:44

邊緣計算數據存儲數據中心

2021-01-15 08:37:28

JAR加載

2024-06-19 10:08:34

GoChannel工具

2014-06-11 13:22:44

2024-07-30 12:24:23

2024-09-06 10:48:13

2024-09-02 09:00:59

2021-09-30 09:21:28

Go語言并發編程

2025-04-02 05:23:00

GoChannel數據

2022-03-04 10:07:45

Go語言字節池

2021-07-02 06:54:45

GoJavachannel

2023-03-09 09:06:13

ChanneGo開發

2023-07-27 13:46:10

go開源項目
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 色资源在线 | 一区二区在线 | 国产精品久久国产精品99 gif | 免费av手机在线观看 | 午夜免费精品视频 | 久久久婷婷 | 91伊人 | 天天射视频 | 亚洲一区二区三区观看 | 久草视频观看 | 中文字幕一区二区三区乱码在线 | 91在线电影 | 欧美不卡一区二区三区 | 毛片毛片毛片毛片 | 激情黄色在线观看 | 夜夜骚 | 日屁网站| 国产自产21区 | 秋霞a级毛片在线看 | 色资源在线 | 羞羞网站在线免费观看 | 国产精品美女一区二区三区 | 人人草天天草 | 国产精品久久久免费 | 国产精品久久国产精品久久 | 黄色片av| 午夜三级视频 | 日本精品一区二区三区四区 | 色又黄又爽网站www久久 | 亚洲一二三区av | 999热视频 | 午夜电影日韩 | 亚洲国产视频一区 | 精品一区二区在线视频 | 精品1区| 久久午夜剧场 | 中文字幕国产 | 亚洲另类春色偷拍在线观看 | www久| 精产国产伦理一二三区 | 午夜影院黄 |