1. 程式人生 > >channel的基本操作

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。

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

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