channal
channel是Go語言在語言級別提供的goroutine間的通信方式筏勒,是一種進程內(nèi)的通信方式沐旨。
在Go中,goroutine和channel是并發(fā)編程的兩大基石,goroutine用來執(zhí)行并發(fā)任務耘斩,channel用來在goroutine之間來傳遞消息。
channal數(shù)據(jù)結(jié)構(gòu)
type hchan struct {
qcount uint // total data in the queue桅咆;chan中的元素總數(shù)
dataqsiz uint // size of the circular queue括授;底層循環(huán)數(shù)組的size
buf unsafe.Pointer // points to an array of dataqsiz elements,指向底層循環(huán)數(shù)組的指針岩饼,只針對有緩沖的channel
elemsize uint16 //chan中元素的大小
closed uint32 //chan是否關閉
elemtype *_type // element type荚虚;元素類型
sendx uint // send index;已發(fā)送元素在循環(huán)數(shù)組中的索引
recvx uint // receive index籍茧;已接收元素在循環(huán)數(shù)組中的索引
recvq waitq // list of recv waiters版述,等待接收消息的goroutine隊列
sendq waitq // list of send waiters眼俊,等待發(fā)送消息的goroutine隊列
// 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
}
緩沖通道
channal基本特征:
- 對于同一個通道岳服,發(fā)送操作之間是互斥的操骡,接收操作之間也是互斥的蒋歌。
- 發(fā)送操作和接收操作中對元素值的處理都是不可分割的拦止。
- 發(fā)送操作在完全完成之前會被阻塞哩照。接收操作也是如此弧烤。
通俗的講就是元素值被完全復制進該通道之后涧黄,其他針對該通道的發(fā)送操作才可能被執(zhí)行漓帚。元素值完全被移出該通道之后母债,其他針對該通道的接收操作才可能被執(zhí)行
- 發(fā)送操作包括了“復制元素值”和“放置副本到通道內(nèi)部”這兩個步驟。
- 接收操作通常包含了“復制通道內(nèi)的元素值”“放置副本到接收方”“刪掉原值”三個步驟
channal通道滿場景
- 發(fā)送操作在這種情況下被阻塞后胰默,所有發(fā)送操作所在的 goroutine 會順序地進入通道內(nèi)部的發(fā)送等待隊列场斑,所以通知的順序總是公平的。
channal通道空場景
接受操作在這種情況下被阻塞后牵署,所有接收操作所在的 goroutine漏隐,都會按照先后順序被放入通道內(nèi)部的接收等待隊列,接受一樣公平奴迅。
非緩沖通道
- 發(fā)送操作以及接受操作一開始就會被阻塞青责。直到發(fā)送接受這一對操作均執(zhí)行挺据。收發(fā)雙方對接上了,就可以傳遞數(shù)據(jù)脖隶。
- 數(shù)據(jù)直接由發(fā)送方復制到接受方扁耐,中間不需要使用非緩沖通道做中轉(zhuǎn)。
差異性
緩沖通道:異步傳送數(shù)據(jù)
非緩沖通道:同步傳送數(shù)據(jù)
channal錯誤使用場景
- 對于值為nil的通道产阱,不論他的類型是什么婉称,對他進行發(fā)送或者接受操作都會處于阻塞狀態(tài)。
- 對于關閉的channal發(fā)送數(shù)據(jù)會發(fā)生panic
- 試圖關閉已經(jīng)關閉的channal會觸發(fā)panic
注意:不要讓接受方關閉通道构蹬,盡量讓發(fā)送方來關閉
單項通道
應用價值:單向通道最主要的用途就是約束其他代碼的行為王暗。
range 遍歷channal
func getIntChan() <-chan int {
num := 5
ch := make(chan int, num)
for i := 0; i < num; i++ {
ch <- i
}
close(ch)
return ch
}
intChan2 := getIntChan()
for elem := range intChan2 {
fmt.Printf("The element in intChan2: %v\n", elem)
}
- for range 能夠從 channal中取出元素值,及時channal關閉也能夠讀取剩余所有元素之后結(jié)束庄敛。
- 如果channal沒有關閉俗壹,channal為空時for range會阻塞直到有新的元素可取,當close channal藻烤,for循環(huán)直接結(jié)束绷雏。
- 如果 channal為nil會永久阻塞
select
select語句的分支分為兩種,一種叫做候選分支怖亭,另一種叫做默認分支
- 如果像上述示例那樣加入了默認分支涎显,那么無論涉及通道操作的表達式是否有阻塞,select語句都不會被阻塞依许。如果那幾個表達式都阻塞了棺禾,或者說都沒有滿足求值的條件缀蹄,那么默認分支就會被選中并執(zhí)行峭跳。
- 如果沒有加入默認分支,那么一旦所有的case表達式都沒有滿足求值條件缺前,那么select語句就會被阻塞蛀醉。直到至少有一個case表達式滿足條件為止。
- select語句只能對其中的每一個case表達式各求值一次衅码。所以拯刁,如果我們想連續(xù)或定時地操作其中的通道的話,就往往需要通過在for語句中嵌入select語句的方式實現(xiàn)逝段。
- 如果select語句發(fā)現(xiàn)同時有多個候選分支滿足選擇條件垛玻,那么它就會用一種偽隨機的算法在這些分支中選擇一個并執(zhí)行。注意奶躯,即使select語句是在被喚醒時發(fā)現(xiàn)的這種情況帚桩,也會這樣做。