通道是Go中的一種一等公民類型。它是Go的招牌特性之一。 和另一個招牌特性協(xié)程一起,這兩個招牌特性使得使用Go進行并發(fā)編程(concurrent programming)變得十分方便和有趣,并且大大降低了并發(fā)編程的難度。
通道的主要作用是用來實現(xiàn)并發(fā)同步。 本篇文章將列出所有的和通道相關(guān)的概念、語法和規(guī)則。為了更好地理解通道,本文也對通道的可能的內(nèi)部實現(xiàn)略加介紹。
本篇文章中的信息量對于Go初學(xué)者來說可能有些密集。本文的某些段落可能需要反復(fù)閱讀幾遍才能有效吸收、消化和理解。
Go語言設(shè)計團隊的首任負責(zé)人Rob Pike對并發(fā)編程的一個建議是不要讓計算通過共享內(nèi)存來通訊,而應(yīng)該讓它們通過通訊來共享內(nèi)存。 通道機制就是這種哲學(xué)的一個設(shè)計結(jié)果。(在Go編程中,我們可以認為一個計算就是一個協(xié)程。)
通過共享內(nèi)存來通訊和通過通訊來共享內(nèi)存是并發(fā)編程中的兩種編程風(fēng)格。 當(dāng)通過共享內(nèi)存來通訊的時候,我們需要一些傳統(tǒng)的并發(fā)同步技術(shù)(比如互斥鎖)來避免數(shù)據(jù)競爭。
Go提供了一種獨特的并發(fā)同步技術(shù)來實現(xiàn)通過通訊來共享內(nèi)存。此技術(shù)即為通道。 我們可以把一個通道看作是在一個程序內(nèi)部的一個先進先出(FIFO:first in first out)數(shù)據(jù)隊列。 一些協(xié)程可以向此通道發(fā)送數(shù)據(jù),另外一些協(xié)程可以從此通道接收數(shù)據(jù)。
隨著一個數(shù)據(jù)值的傳遞(發(fā)送和接收),一些數(shù)據(jù)值的所有權(quán)從一個協(xié)程轉(zhuǎn)移到了另一個協(xié)程。 當(dāng)一個協(xié)程發(fā)送一個值到一個通道,我們可以認為此協(xié)程釋放了(通過此發(fā)送值可以訪問到的)一些值的所有權(quán)。 當(dāng)一個協(xié)程從一個通道接收到一個值,我們可以認為此協(xié)程獲取了(通過此接受值可以訪問到的)一些值的所有權(quán)。
當(dāng)然,在通過通道傳遞數(shù)據(jù)的時候,也可能沒有任何所有權(quán)發(fā)生轉(zhuǎn)移。
所有權(quán)發(fā)生轉(zhuǎn)移的值常常被傳遞的值所引用著,但有時候也并非如此。 在Go中,數(shù)據(jù)所有權(quán)的轉(zhuǎn)移并非體現(xiàn)在語法上,而是體現(xiàn)在邏輯上。 Go通道可以幫助程序員輕松地避免數(shù)據(jù)競爭,但不會防止程序員因為犯錯而寫出錯誤的并發(fā)代碼的情況發(fā)生。
盡管Go也支持幾種傳統(tǒng)的數(shù)據(jù)同步技術(shù),但是只有通道為一等公民。 通道是Go中的一種類型,所以我們可以無需引進任何代碼包就可以使用通道。 幾種傳統(tǒng)的數(shù)據(jù)同步技術(shù)提供在sync
和sync/atomic
標準庫包中。
實事求是地說,每種并發(fā)同步技術(shù)都有它們各自的最佳應(yīng)用場景,但是通道的應(yīng)用范圍更廣。 使用通道來做同步常??梢允沟么a看上去更整潔和易于理解。
通道的一個問題是通道的編程體驗常常很有趣以至于程序員們經(jīng)常在并非是通道的最佳應(yīng)用場景中仍堅持使用通道。
和數(shù)組、切片以及映射類型一樣,每個通道類型也有一個元素類型。 一個通道只能傳送它的(通道類型的)元素類型的值。
通道可以是雙向的,也可以是單向的。
chan T
表示一個元素類型為T
的雙向通道類型。 編譯器允許從此類型的值中接收和向此類型的值中發(fā)送數(shù)據(jù)。chan<- T
表示一個元素類型為T
的單向發(fā)送通道類型。 編譯器不允許從此類型的值中接收數(shù)據(jù)。<-chan T
表示一個元素類型為T
的單向接收通道類型。 編譯器不允許向此類型的值中發(fā)送數(shù)據(jù)。雙向通道chan T
的值可以被隱式轉(zhuǎn)換為單向通道類型chan<- T
和<-chan T
,但反之不行(即使顯式也不行)。 類型chan<- T
和<-chan T
的值也不能相互轉(zhuǎn)換。
每個通道值有一個容量屬性。此屬性的意義將在下一節(jié)中得到解釋。 一個容量為0的通道值稱為一個非緩沖通道(unbuffered channel),一個容量不為0的通道值稱為一個緩沖通道(buffered channel)。
通道類型的零值也使用預(yù)聲明的nil
來表示。 一個非零通道值必須通過內(nèi)置的make
函數(shù)來創(chuàng)建。 比如make(chan int, 10)
將創(chuàng)建一個元素類型為int
的通道值。 第二個參數(shù)指定了欲創(chuàng)建的通道的容量。此第二個實參是可選的,它的默認值為0
。
所有通道類型均為可比較類型。
從值部一文,我們了解到一個通道值可能含有底層部分。 當(dāng)一個通道值被賦給另一個通道值后,這兩個通道值將共享相同的底層部分。 換句話說,這兩個通道引用著同一個底層的內(nèi)部通道對象。 比較這兩個通道的結(jié)果為true
。
Go中有五種通道相關(guān)的操作。假設(shè)一個通道(值)為ch
,下面列出了這五種操作的語法或者函數(shù)調(diào)用。
close
來關(guān)閉一個通道:
close(ch)
傳給close
函數(shù)調(diào)用的實參必須為一個通道值,并且此通道值不能為單向接收的。
ch
發(fā)送一個值v
:
ch <- v
v
必須能夠賦值給通道ch
的元素類型。 ch
不能為單向接收通道。 <-
稱為數(shù)據(jù)發(fā)送操作符。
ch
接收一個值:
<-ch
如果一個通道操作不永久阻塞,它總會返回至少一個值,此值的類型為通道ch
的元素類型。 ch
不能為單向發(fā)送通道。 <-
稱為數(shù)據(jù)接收操作符,是的它和數(shù)據(jù)發(fā)送操作符的表示形式是一樣的。 在大多數(shù)場合下,一個數(shù)據(jù)接收操作可以被認為是一個單值表達式。 但是,當(dāng)一個數(shù)據(jù)接收操作被用做一個賦值語句中的唯一的源值的時候,它可以返回第二個可選的類型不確定的布爾值返回值從而成為一個多值表達式。 此類型不確定的布爾值表示第一個接收到的值是否是在通道被關(guān)閉前發(fā)送的。
(從后面的章節(jié),我們將得知我們可以從一個已關(guān)閉的通道中接收到無窮個值。) 數(shù)據(jù)接收操作在賦值中被用做源值的例子:
v = <-ch
v, sentBeforeClosed = <-ch
cap(ch)
其中cap
是一個已經(jīng)在容器類型一文中介紹過的內(nèi)置函數(shù)。 cap
的返回值的類型為內(nèi)置類型int
。
len(ch)
其中len
是一個已經(jīng)在容器類型一文中介紹過的內(nèi)置函數(shù)。 len
的返回值的類型也為內(nèi)置類型int
。 一個通道的長度是指當(dāng)前有多少個已被發(fā)送到此通道但還未被接收出去的元素值。
Go中大多數(shù)的基本操作都是未同步的。換句話說,它們都不是并發(fā)安全的。 這些操作包括賦值、傳參、和各種容器值操作等。 但是,上面列出的五種通道相關(guān)的操作都已經(jīng)同步過了,因此它們可以在并發(fā)協(xié)程中安全運行而無需其它同步操作。
注意:通道的賦值和其它類型值的賦值一樣,是未同步的。 同樣,將剛從一個通道接收出來的值賦給另一個值也是未同步的。
如果被查詢的通道為一個nil零值通道,則cap
和len
函數(shù)調(diào)用都返回0
。 這兩個操作是如此簡單,所以后面將不再對它們進行詳解。 事實上,這兩個操作在實踐中很少使用。
通道的發(fā)送、接收和關(guān)閉操作將在下一節(jié)得到詳細解釋。
為了讓解釋簡單清楚,在本文后續(xù)部分,通道將被歸為三類:
下表簡單地描述了三種通道操作施加到三類通道的結(jié)果。
操作 | 一個零值nil通道 | 一個非零值但已關(guān)閉的通道 | 一個非零值且尚未關(guān)閉的通道 |
---|---|---|---|
關(guān)閉 | 產(chǎn)生恐慌 | 產(chǎn)生恐慌 | 成功關(guān)閉(C) |
發(fā)送數(shù)據(jù) | 永久阻塞 | 產(chǎn)生恐慌 | 阻塞或者成功發(fā)送(B) |
接收數(shù)據(jù) | 永久阻塞 | 永不阻塞(D) | 阻塞或者成功接收(A) |
對于上表中的五種未打上標的情形,規(guī)則很簡單:
下面將詳細解釋其它四種被打了上標(A/B/C/D)的情形。
為了更好地理解通道和為了后續(xù)講解方便,先了解一下通道類型的大致內(nèi)部實現(xiàn)是很有幫助的。
我們可以認為一個通道內(nèi)部維護了三個隊列(均可被視為先進先出隊列):
每個通道內(nèi)部維護著一個互斥鎖用來在各種通道操作中防止數(shù)據(jù)競爭。
通道操作情形A: 當(dāng)一個協(xié)程R
嘗試從一個非零且尚未關(guān)閉的通道接收數(shù)據(jù)的時候,此協(xié)程R
將首先嘗試獲取此通道的鎖,成功之后將執(zhí)行下列步驟,直到其中一個步驟的條件得到滿足。
R
將從緩沖隊列取出(接收)一個值。 如果發(fā)送數(shù)據(jù)協(xié)程隊列不為空,一個發(fā)送協(xié)程將從此隊列中彈出,此協(xié)程欲發(fā)送的值將被推入緩沖隊列。此發(fā)送協(xié)程將恢復(fù)至運行狀態(tài)。 接收數(shù)據(jù)協(xié)程R
繼續(xù)運行,不會阻塞。對于這種情況,此數(shù)據(jù)接收操作為一個非阻塞操作。R
接收。此發(fā)送協(xié)程將恢復(fù)至運行狀態(tài)。 接收數(shù)據(jù)協(xié)程R
繼續(xù)運行,不會阻塞。對于這種情況,此數(shù)據(jù)接收操作為一個非阻塞操作。R
將被推入接收數(shù)據(jù)協(xié)程隊列,并進入阻塞狀態(tài)。 它以后可能會被另一個發(fā)送數(shù)據(jù)協(xié)程喚醒而恢復(fù)運行。 對于這種情況,此數(shù)據(jù)接收操作為一個阻塞操作。 通道操作情形B: 當(dāng)一個協(xié)程S
嘗試向一個非零且尚未關(guān)閉的通道發(fā)送數(shù)據(jù)的時候,此協(xié)程S
將首先嘗試獲取此通道的鎖,成功之后將執(zhí)行下列步驟,直到其中一個步驟的條件得到滿足。
S
發(fā)送的值。此接收協(xié)程將恢復(fù)至運行狀態(tài)。 發(fā)送數(shù)據(jù)協(xié)程S
繼續(xù)運行,不會阻塞。對于這種情況,此數(shù)據(jù)發(fā)送操作為一個非阻塞操作。S
欲發(fā)送的值將被推入緩沖隊列,發(fā)送數(shù)據(jù)協(xié)程S
繼續(xù)運行,不會阻塞。 對于這種情況,此數(shù)據(jù)發(fā)送操作為一個非阻塞操作。S
將被推入發(fā)送數(shù)據(jù)協(xié)程隊列,并進入阻塞狀態(tài)。 它以后可能會被另一個接收數(shù)據(jù)協(xié)程喚醒而恢復(fù)運行。 對于這種情況,此數(shù)據(jù)發(fā)送操作為一個阻塞操作。上面已經(jīng)提到過,一旦一個非零通道被關(guān)閉,繼續(xù)向此通道發(fā)送數(shù)據(jù)將產(chǎn)生一個恐慌。 注意,向關(guān)閉的通道發(fā)送數(shù)據(jù)屬于一個非阻塞操作。
通道操作情形C: 當(dāng)一個協(xié)程成功獲取到一個非零且尚未關(guān)閉的通道的鎖并且準備關(guān)閉此通道時,下面兩步將依次執(zhí)行:
-race
)打開時,Go官方標準運行時將很可能會對并發(fā)地關(guān)閉一個通道和向此通道發(fā)送數(shù)據(jù)這種情形報告成數(shù)據(jù)競爭。注意:當(dāng)一個緩沖隊列不為空的通道被關(guān)閉之后,它的緩沖隊列不會被清空,其中的數(shù)據(jù)仍然可以被后續(xù)的數(shù)據(jù)接收操作所接收到。詳見下面的對情形D的解釋。
通道操作情形D: 一個非零通道被關(guān)閉之后,此通道上的后續(xù)數(shù)據(jù)接收操作將永不會阻塞。 此通道的緩沖隊列中存儲數(shù)據(jù)仍然可以被接收出來。 伴隨著這些接收出來的緩沖數(shù)據(jù)的第二個可選返回(類型不確定布爾)值仍然是true
。 一旦此緩沖隊列變?yōu)榭眨罄m(xù)的數(shù)據(jù)接收操作將永不阻塞并且總會返回此通道的元素類型的零值和值為false
的第二個可選返回結(jié)果。 上面已經(jīng)提到了,一個接收操作的第二個可選返回(類型不確定布爾)結(jié)果表示一個接收到的值是否是在此通道被關(guān)閉之前發(fā)送的。
如果此返回值為false
,則第一個返回值必然是一個此通道的元素類型的零值。
知道哪些通道操作是阻塞的和哪些是非阻塞的對正確理解后面將要介紹的select
流程控制機制非常重要。
如果一個協(xié)程被從一個通道的某個隊列中(不論發(fā)送數(shù)據(jù)協(xié)程隊列還是接收數(shù)據(jù)協(xié)程隊列)彈出,并且此協(xié)程是在一個select控制流程中推入到此隊列的,那么此協(xié)程將在下面將要講解的select控制流程的執(zhí)行步驟中的第9步中恢復(fù)至運行狀態(tài),并且同時它會被從相應(yīng)的select
控制流程中的相關(guān)的若干通道的協(xié)程隊列中移除掉。
根據(jù)上面的解釋,我們可以得出如下的關(guān)于一個通道的內(nèi)部的三個隊列的各種事實:
來看一些通道的使用例子來加深一下對上一節(jié)中的解釋的理解。
一個簡單的通過一個非緩沖通道實現(xiàn)的請求/響應(yīng)的例子:
package main
import (
"fmt"
"time"
)
func main() {
c := make(chan int) // 一個非緩沖通道
go func(ch chan<- int, x int) {
time.Sleep(time.Second)
// <-ch // 此操作編譯不通過
ch <- x*x // 阻塞在此,直到發(fā)送的值被接收
}(c, 3)
done := make(chan struct{})
go func(ch <-chan int) {
n := <-ch // 阻塞在此,直到有值發(fā)送到c
fmt.Println(n) // 9
// ch <- 123 // 此操作編譯不通過
time.Sleep(time.Second)
done <- struct{}{}
}(c)
<-done // 阻塞在此,直到有值發(fā)送到done
fmt.Println("bye")
}
輸出結(jié)果:
9
bye
下面的例子使用了一個緩沖通道。此例子程序并非是一個并發(fā)程序,它只是為了展示緩沖通道的使用。
package main
import "fmt"
func main() {
c := make(chan int, 2) // 一個容量為2的緩沖通道
c <- 3
c <- 5
close(c)
fmt.Println(len(c), cap(c)) // 2 2
x, ok := <-c
fmt.Println(x, ok) // 3 true
fmt.Println(len(c), cap(c)) // 1 2
x, ok = <-c
fmt.Println(x, ok) // 5 true
fmt.Println(len(c), cap(c)) // 0 2
x, ok = <-c
fmt.Println(x, ok) // 0 false
x, ok = <-c
fmt.Println(x, ok) // 0 false
fmt.Println(len(c), cap(c)) // 0 2
close(c) // 此行將產(chǎn)生一個恐慌
c <- 7 // 如果上一行不存在,此行也將產(chǎn)生一個恐慌。
}
一場永不休場的足球比賽:
package main
import (
"fmt"
"time"
)
func main() {
var ball = make(chan string)
kickBall := func(playerName string) {
for {
fmt.Print(<-ball, "傳球", "\n")
time.Sleep(time.Second)
ball <- playerName
}
}
go kickBall("張三")
go kickBall("李四")
go kickBall("王二麻子")
go kickBall("劉大")
ball <- "裁判" // 開球
var c chan bool // 一個零值nil通道
<-c // 永久阻塞在此
}
請閱讀通道用例大全來查看更多通道的使用例子。
在一個值被從一個協(xié)程傳遞到另一個協(xié)程的過程中,此值將被復(fù)制至少一次。 如果此傳遞值曾經(jīng)在某個通道的緩沖隊列中停留過,則它在此傳遞過程中將被復(fù)制兩次。 一次復(fù)制發(fā)生在從發(fā)送協(xié)程向緩沖隊列推入此值的時候,另一個復(fù)制發(fā)生在接收協(xié)程從緩沖隊列取出此值的時候。 和賦值以及函數(shù)調(diào)用傳參一樣,當(dāng)一個值被傳遞時,只有它的直接部分被復(fù)制。
對于官方標準編譯器,最大支持的通道的元素類型的尺寸為65535
。 但是,一般說來,為了在數(shù)據(jù)傳遞過程中避免過大的復(fù)制成本,我們不應(yīng)該使用尺寸很大的通道元素類型。 如果欲傳送的值的尺寸較大,應(yīng)該改用指針類型做為通道的元素類型。
注意,一個通道被其發(fā)送數(shù)據(jù)協(xié)程隊列和接收數(shù)據(jù)協(xié)程隊列中的所有協(xié)程引用著。因此,如果一個通道的這兩個隊列只要有一個不為空,則此通道肯定不會被垃圾回收。 另一方面,如果一個協(xié)程處于一個通道的某個協(xié)程隊列之中,則此協(xié)程也肯定不會被垃圾回收,即使此通道僅被此協(xié)程所引用。 事實上,一個協(xié)程只有在退出后才能被垃圾回收。
數(shù)據(jù)接收和發(fā)送操作都屬于簡單語句。 另外一個數(shù)據(jù)接收操作總是可以被用做一個單值表達式。 簡單語句和表達式可以被用在一些控制流程的某些部分。
在下面這個例子中,數(shù)據(jù)接收和發(fā)送操作被用在兩個for
循環(huán)的初始化和步尾語句。
package main
import (
"fmt"
"time"
)
func main() {
fibonacci := func() chan uint64 {
c := make(chan uint64)
go func() {
var x, y uint64 = 0, 1
for ; y < (1 << 63); c <- y { // 步尾語句
x, y = y, x+y
}
close(c)
}()
return c
}
c := fibonacci()
for x, ok := <-c; ok; x, ok = <-c { // 初始化和步尾語句
time.Sleep(time.Second)
fmt.Println(x)
}
}
for-range
循環(huán)控制流程也適用于通道。 此循環(huán)將不斷地嘗試從一個通道接收數(shù)據(jù),直到此通道關(guān)閉并且它的緩沖隊列為空為止。 和應(yīng)用于數(shù)組/切片/映射的for-range
語法不同,應(yīng)用于通道的for-range
語法中最多只能出現(xiàn)一個循環(huán)變量,此循環(huán)變量用來存儲接收到的值。
for v := range aChannel {
// 使用v
}
等價于
for {
v, ok = <-aChannel
if !ok {
break
}
// 使用v
}
當(dāng)然,這里的通道aChannel
一定不能為一個單向發(fā)送通道。 如果它是一個nil零值,則此for-range
循環(huán)將使當(dāng)前協(xié)程永久阻塞。
上一節(jié)中的例子中的最后一個for
循環(huán)可以改寫為下面這樣:
for x := range c {
time.Sleep(time.Second)
fmt.Println(x)
}
Go中有一個專門為通道設(shè)計的select-case
分支流程控制語法。 此語法和switch-case
分支流程控制語法很相似。 比如,select-case
流程控制代碼塊中也可以有若干case
分支和最多一個default
分支。 但是,這兩種流程控制也有很多不同點。在一個select-case
流程控制中,
select
關(guān)鍵字和{
之間不允許存在任何表達式和語句。fallthrough
語句不能被使用.case
關(guān)鍵字后必須跟隨一個通道接收數(shù)據(jù)操作或者一個通道發(fā)送數(shù)據(jù)操作。 通道接收數(shù)據(jù)操作可以做為源值出現(xiàn)在一條簡單賦值語句中。 以后,一個case
關(guān)鍵字后跟隨的通道操作將被稱為一個case
操作。case
操作中將有一個被隨機選擇執(zhí)行(而不是按照從上到下的順序),然后執(zhí)行此操作對應(yīng)的case
分支代碼塊。case
操作均為阻塞的情況下,如果default
分支存在,則default
分支代碼塊將得到執(zhí)行; 否則,當(dāng)前協(xié)程將被推入所有阻塞操作中相關(guān)的通道的發(fā)送數(shù)據(jù)協(xié)程隊列或者接收數(shù)據(jù)協(xié)程隊列中,并進入阻塞狀態(tài)。按照上述規(guī)則,一個不含任何分支的select-case
代碼塊select{}
將使當(dāng)前協(xié)程處于永久阻塞狀態(tài)。
在下面這個例子中,default
分支將鐵定得到執(zhí)行,因為兩個case
分支后的操作均為阻塞的。
package main
import "fmt"
func main() {
var c chan struct{} // nil
select {
case <-c: // 阻塞操作
case c <- struct{}{}: // 阻塞操作
default:
fmt.Println("Go here.")
}
}
下面這個例子中實現(xiàn)了嘗試發(fā)送(try-send)和嘗試接收(try-receive)。 它們都是用含有一個case
分支和一個default
分支的select-case
代碼塊來實現(xiàn)的。
package main
import "fmt"
func main() {
c := make(chan string, 2)
trySend := func(v string) {
select {
case c <- v:
default: // 如果c的緩沖已滿,則執(zhí)行默認分支。
}
}
tryReceive := func() string {
select {
case v := <-c: return v
default: return "-" // 如果c的緩沖為空,則執(zhí)行默認分支。
}
}
trySend("Hello!") // 發(fā)送成功
trySend("Hi!") // 發(fā)送成功
trySend("Bye!") // 發(fā)送失敗,但不會阻塞。
// 下面這兩行將接收成功。
fmt.Println(tryReceive()) // Hello!
fmt.Println(tryReceive()) // Hi!
// 下面這行將接收失敗。
fmt.Println(tryReceive()) // -
}
下面這個程序有50%的幾率會因為恐慌而崩潰。 此程序中select-case
代碼塊中的兩個case
操作均不阻塞,所以隨機一個將被執(zhí)行。 如果第一個case
操作(向已關(guān)閉的通道發(fā)送數(shù)據(jù))被執(zhí)行,則一個恐慌將產(chǎn)生。
package main
func main() {
c := make(chan struct{})
close(c)
select {
case c <- struct{}{}: // 若此分支被選中,則產(chǎn)生一個恐慌
case <-c:
}
}
select-case
流程控制是Go中的一個重要和獨特的特性。 下面列出了官方標準運行時中select-case
流程控制的實現(xiàn)步驟。
case
操作中涉及到的通道表達式和發(fā)送值表達式按照從上到下,從左到右的順序一一估值。 在賦值語句中做為源值的數(shù)據(jù)接收操作對應(yīng)的目標值在此時刻不需要被估值。default
分支總是排在最后。 所有case
操作中相關(guān)的通道可能會有重復(fù)的。case
操作中相關(guān)的通道進行排序。 排序依據(jù)并不重要,官方Go標準編譯器使用通道的地址順序進行排序。 排序結(jié)果中前N
個通道不存在重復(fù)的情況。 N
為所有case
操作中涉及到的不重復(fù)的通道的數(shù)量。 下面,通道鎖順序是針對此排序結(jié)果中的前N
個通道來說的,通道鎖逆序是指此順序的逆序。case
分支并且相應(yīng)的通道操作是一個向關(guān)閉了的通道發(fā)送數(shù)據(jù)操作,則按照通道鎖逆序解鎖所有的通道并在當(dāng)前協(xié)程中產(chǎn)生一個恐慌。 跳到第12步。case
分支并且相應(yīng)的通道操作是非阻塞的,則按照通道鎖逆序解鎖所有的通道并執(zhí)行相應(yīng)的case
分支代碼塊。 (此相應(yīng)的通道操作可能會喚醒另一個處于阻塞狀態(tài)的協(xié)程。) 跳到第12步。default
分支,則按照通道鎖逆序解鎖所有的通道并執(zhí)行此default
分支代碼塊。 跳到第12步。default
分支肯定是不存在的,并且所有的case
操作均為阻塞的。)case
分支信息)推入到每個case
操作中對應(yīng)的通道的發(fā)送數(shù)據(jù)協(xié)程隊列或接收數(shù)據(jù)協(xié)程隊列中。 當(dāng)前協(xié)程可能會被多次推入到同一個通道的這兩個隊列中,因為多個case
操作中對應(yīng)的通道可能為同一個。select-case
流程中)肯定有一個相應(yīng)case
操作與之配合傳遞數(shù)據(jù)。 在此配合過程中,當(dāng)前協(xié)程將從相應(yīng)case
操作相關(guān)的通道的接收/發(fā)送數(shù)據(jù)協(xié)程隊列中彈出。case
操作中對應(yīng)的通道的發(fā)送數(shù)據(jù)協(xié)程隊列或接收數(shù)據(jù)協(xié)程隊列中(可能以非彈出的方式)移除。
case
分支已經(jīng)在第9步中知曉。 按照通道鎖逆序解鎖所有的通道并執(zhí)行此case
分支代碼塊。從此實現(xiàn)中,我們得知
select-case
流程控制中并在以后被喚醒時,它可能會從多個通道的發(fā)送數(shù)據(jù)協(xié)程隊列和接收數(shù)據(jù)協(xié)程隊列中被移除。
我們可以在通道用例大全一文中找到更多通道的使用例子。
盡管通道可以幫助我們輕松地寫出正確的并發(fā)代碼,和其它并發(fā)同步技術(shù)一樣,通道并不會阻止我們寫出不正確的并發(fā)代碼。
通道并非在任何場合總是最佳的并發(fā)同步方案,請閱讀其它并發(fā)同步技術(shù)和原子操作來了解Go中支持的更多的并發(fā)同步技術(shù)。
更多建議: