channel的基本操作

激動,終於學到了Go最初吸引我的知識了。

channel作爲Go語言最有特色的數據類型,和goroutine並駕齊驅,共同代表了Go語言獨有的併發編程模式和編程哲學:

Don't communicate by sharing memory; share memory by communicating.(不要通過共享內存來通信,而應該通過通信來共享內存。)——Rob Pike Go語言主要創造者之一。

channel是後半句話的完美實現。我們可以利用通道在多個goroutine之間傳遞數據。

通道類型的值本身就是併發安全的,這是Go語言自帶的、唯一一個可以滿足併發安全性的類型。

創建channel

c := make(chan int)

fmt.Println(len(c))  //0
fmt.Println(cap(c))  //0

make(chan int):chan是Go的關鍵字,表示通道,int說明了該通道的元素類型。

make(chan int , 2)
fmt.Println(len(c))  //0
fmt.Println(cap(c))  //2

這行代碼表示創建一個容量爲2的元素類型爲int的通道。

容量這個參數(不能小於0),是可選的,所謂的容量是指通道可以醉倒緩存多少個元素值。通道的長度是指,當前通道中元素的數量。

通道的分類

當容量爲0時,叫做非緩衝通道。 當容量大於0時,叫做緩衝通道。

通道的底層數據結構是環形鏈表。

一個通道相當於一個先進先出的隊列。也就是說,通道中的各個元素值都是嚴格地按照發送的順序排列的,先被髮送進通道的元素值一定會先被接收。

元素值的發送和接收都需要用到接送操作符<-

//發送表達式

c := make(chan int ,1)
c <- 1

//接收表達式

value,ok := <- c

對通道的發送和接收操作都有哪些基本的特性?

回答:

  1. 對於同一個通道,發送操作之間是互斥的,接收操作之間也是互斥的
  2. 發送操作和接收操作過程中對元素值的處理都是不可分割的
  3. 發送操作在完全完成之前會被阻塞。接收操作也是如此。

對於同一個通道,發送操作之間是互斥的,接收操作之間也是互斥的

同一時刻,有多個對同一個通道的發送操作,Go語言的運行時系統只會執行其中一個。知道這個元素值被複制進該通道之後,其他的針對該通道的發送操作纔可能被執行。

類似的,在同一個時刻,運行時系統也是會執行對同一個通道的人一個接收操作中的某一個。直到這個元素值被完全移出該通道之後,其他的對該通道的接收操作纔可能被執行。即使這些操作是併發執行的也是如此。

另外,對於通道中的同一個元素值來說,發送操作和接收操作之間也是互斥的。正在被複制但是還沒有完全複製進通道的元素值,是不可能被想接收它的一方看到和取走。

注意這裏的一個細節:元素值從外界被複制進通道。意思是進入通道的是副本。這裏的複製是淺複製。

元素值從通道進入外界時,這個操作包含了兩步:

  • 生成通道中這個元素值的副本,並準備給到接收方
  • 刪除通道中的這個元素值。

發送操作和接收操作過程中對元素值的處理都是不可分割的

發送操作要麼還沒有複製元素值,要麼已經複製完畢,絕對不會出現只複製了一部分的情況。

接收操作的複製通道中的副本,將副本發送給接收方,刪除通道中的元素值這一系列動作是一氣呵成的,絕不會被打斷。

發送操作在完全完成之前會被阻塞。接收操作也是如此。

發送操作

一般情況下,發送操作包括了“複製元素副本”和“放置副本到通道內部”這兩個步驟,這兩個步驟完全完成之前,發起這個發送操作的那句代碼會一直阻塞在那裏。這句代碼之後的代碼不會有執行的機會,直到這句代碼的阻塞解除。

更標準的說法是:在通道完成發送操作之後,運行時系統會通知這句代碼所在的goroutine,使這個goroutine去爭取繼續運行代碼的機會。

接收操作

接收操作通常包含了“複製通道內的元素值”“放置副本到接收方”“刪掉原值”三個步驟。

在所有這些步驟完全完成之前,發起該操作的代碼也會一直阻塞,直到該代碼所在的 goroutine收到了運行時系統的通知並重新獲得運行機會爲止。

發送操作和接收操作在什麼時候可能被長時間的阻塞?

緩衝通道

通道已滿:

對它的所有發送操作都會被阻塞,發送操作所在的goroutine會順序地進入通道內部的“發送等待隊列”,直到通道中有元素被接收。這時,通道會優先通知最早等待發送的goroutine,這個goroutine會再次執行發送操作。

通道已空

對它的所有接收操作都會被阻塞,接收操作所在的goroutine會按照先後順序被放入通道內部的“接收等待隊列”,直到通道中有新的元素值出現。通道就會通知最早等待的那個接收操作所在的goroutine,並使它再次執行接收操作。

非緩衝通道

無論是發送操作還是接收操作,一開始執行就會被阻塞,直到配對的操作也開始執行,纔會繼續傳遞。並且,數據是直接從發送發覆制到接收方的,中間不會用非緩衝通道做中轉。

由此可見:非緩衝通道是在用同步的方式傳遞數據。也就是說,只有接收雙發對接上了,數據纔會被傳遞。

相比之下,緩衝通道則是在用異步的方式傳遞數據。在大多數情況下,緩衝通道會作爲收發雙方的中間件,元素值會先從發送發覆制到緩衝通道,之後再由緩衝通道複製給接收方。但是,當發送操作在執行的時候如果發現空的通道中,正好有等待的接收方,那麼它會直接把元素複製給接收方。

值爲nil的通道

不論它的具體類型是什麼,對它的發送操作和接收操作都會永久地處於阻塞狀態,它們所屬的goroutine中的任何代碼,都不再會別執行。

發送操作和接收操作在什麼時候會引發panic?

  1. 通道一旦關閉,再對它進行發送操作,就會引發panic。
  2. 如果試圖關閉一個已經關閉了的通道,也會引發panic。

注意,接收方是可以感知到通道的關閉的,並能夠安全退出。

value, ok := <- c  //接收表達式

如果ok是false,說明通道已經關閉,並且沒有元素可取了。

有一種情況:通道關閉時,裏面還有元素未被取出,那麼接收表達式的第一個結果,仍會是通道中的某一個元素,第二個結果一定是true。

考慮上面的情況,我們不能依靠接收表達式的第二個結果值來判斷通道是否關閉。

考慮到通道的收發操作有如上的特性,所以除非有特殊的保障措施,否則,我們千萬不要讓接收方關閉通道,而應該讓發送發關閉通道。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章