基礎(chǔ)用法
channel
ch := make(chan int)
senderOnly := make(chan<- int) // 只能用來發(fā)送(管道的入口,只進(jìn)不出)
receiverOnly := make(<-chan int) // 只能用來接收(管道的出口,只出不進(jìn))
unbuffer := make(chan int) // 無緩沖可收發(fā)
buffer := make(chan int, 2) // 有緩沖可收發(fā)
chan T是雙向channel類型,編譯器允許對雙向channel同時(shí)進(jìn)行發(fā)送和接收。
chan<- T是只寫channel類型,編譯器只允許往channel里面發(fā)送數(shù)據(jù)。
<-chan T是只讀channel類型,編輯器只允許從channel里面接收數(shù)據(jù)。
所謂單向channel只是對channel的一種使用限制,實(shí)際定義單向通道沒有意義,一般用在限制函數(shù)返回值或者函數(shù)參數(shù)。
func readChan(chanName <-chan int): 通過形參限定函數(shù)內(nèi)部只能從channel中讀取數(shù)據(jù)
func writeChan(chanName chan<- int): 通過形參限定函數(shù)內(nèi)部只能向channel中寫入數(shù)據(jù)
源碼位置:https://github.com/golang/go/blob/master/src/runtime/chan.go
源碼
type hchan struct {
qcount uint // 當(dāng)前隊(duì)列中剩余元素
dataqsiz uint // 環(huán)形隊(duì)列長度
buf unsafe.Pointer // 環(huán)形隊(duì)列指針
elemsize uint16
closed uint32
elemtype *_type // element type
sendx uint // 寫入時(shí)存放到隊(duì)列中的位置
recvx uint // 隊(duì)列的該位置讀出
recvq waitq // 等待讀消息的goroutine隊(duì)列
sendq waitq // 等待寫消息的goroutine隊(duì)列
lock mutex // 鎖保護(hù)
}
type waitq struct {
first *sudog
last *sudog
}
環(huán)形隊(duì)列(緩沖通道)
chan內(nèi)部實(shí)現(xiàn)了一個(gè)環(huán)形隊(duì)列作為其緩沖區(qū),隊(duì)列的長度是創(chuàng)建chan時(shí)指定的。下圖展示了一個(gè)可緩存6個(gè)元素的channel示意圖:

dataqsiz指示了隊(duì)列長度為6,即可緩存6個(gè)元素;
buf指向隊(duì)列的內(nèi)存,隊(duì)列中還剩余兩個(gè)元素;
qcount表示隊(duì)列中還有兩個(gè)元素;
sendx指示后續(xù)寫入的數(shù)據(jù)存儲的位置,取值[0, 6);
recvx指示從該位置讀取數(shù)據(jù), 取值[0, 6);
等待隊(duì)列(無緩沖通道)
從channel讀數(shù)據(jù),如果channel緩沖區(qū)為空或者沒有緩沖區(qū),當(dāng)前goroutine會(huì)被阻塞。 向channel寫數(shù)據(jù),如果channel緩沖區(qū)已滿或者沒有緩沖區(qū),當(dāng)前goroutine會(huì)被阻塞。
下圖展示了一個(gè)沒有緩沖區(qū)的channel,有幾個(gè)goroutine阻塞等待讀數(shù)據(jù):

一般情況下recvq和sendq至少有一個(gè)為空。只有一個(gè)例外,那就是同一個(gè)goroutine使用select語句向channel一邊寫數(shù)據(jù),一邊讀數(shù)據(jù)。
關(guān)閉channel
關(guān)閉channel時(shí)會(huì)把recvq中的G全部喚醒,會(huì)讀取完已經(jīng)放入的數(shù)據(jù)后關(guān)閉。把sendq中的G全部喚醒,但這些G會(huì)panic。
除此之外,panic出現(xiàn)的常見場景還有: 1. 關(guān)閉值為nil的channel 2. 關(guān)閉已經(jīng)被關(guān)閉的channel 3. 向已經(jīng)關(guān)閉的channel寫數(shù)據(jù)
package main
import (
"fmt"
"time"
)
func main() {
chan1 := make(chan int, 9)
go func() {
chan1 <- 1
chan1 <- 2
chan1 <- 3
chan1 <- 4
close(chan1)
fmt.Println("已經(jīng)關(guān)閉")
time.Sleep(5 * time.Second)
}()
for {
x, ok := <-chan1
if !ok {
fmt.Println("關(guān)閉")
}
fmt.Println(x)
time.Sleep(2 * time.Second)
}
}
已經(jīng)關(guān)閉
1
2
3
4
關(guān)閉
0