Channel 管道
1 初始化
可用var聲明nil管道;用make初始化管道;
len(): 緩沖區中元素個數, cap(): 緩沖區大小
//變量聲明
var a chan int
//使用make初始化
b := make(chan int) //不帶緩沖區
c := make(chan string,2) // 帶緩沖區
ch1 := make(chan int) // 0 0
ch2 := make(chan int, 2)// 1 2
ch2 <- 1
fmt.Println(len(ch1), len(ch2), cap(ch1), cap(ch2))
2 讀寫操作
用 " <- "來表示數據流向,緩沖區滿時寫/緩沖區空時讀 都會阻塞,直到被其他攜程喚醒
a := make(chan int, 3)
a <- 1 //數據寫入管道
<-a //管道讀出數據
管道默認雙向可讀寫,但也可在創建函數時顯示單向讀寫
func write(ch chan<- int,a int) {ch <- a// <- ch 無效運算: <- ch (從僅發送類型 chan<- int 接收)
}func read(ch <-chan int) {<- ch//ch <- 1 無效運算: ch <- 1 (發送到僅接收類型 <-chan int)
}
讀寫值為nil的管道,會永久阻塞,觸發死鎖
var ch chan intch <- 1 // fatal error: all goroutines are asleep - deadlock!<-ch // fatal error: all goroutines are asleep - deadlock!
讀寫已關閉管道:有緩沖區成功可讀緩沖區內容,無緩沖區讀零值并返回false;寫已關閉管道會觸發panic
關閉后,等待隊列中的攜程全部喚醒,按照上述規則直接返回
ch1 := make(chan int)
ch2 := make(chan int, 2)
go func() {ch1 <- 1
}()
ch2 <- 2
close(ch1)
close(ch2)
v1, b1 := <-ch1 //0 false
v2, b2 := <-ch2 //2 true
println(v1, v2, b1, b2)
ch1 <- 1 //panic: send on closed channel
ch2 <- 1 //panic: send on closed channel
3 實現原理
簡單來說,channel底層是通過環形隊列來實現其緩沖區的功能。再加上兩個等待隊列來存除被堵塞的攜程。最后加上互斥鎖,保證其并發安全
type hchan struct {
qcount uint // 隊列中數據的總數
dataqsiz uint // 環形隊列的大小
buf unsafe.Pointer // 指向底層的環形隊列
elemsize uint16 // 元素的大小(以字節為單位)
closed uint32 // 表示通道是否已關閉
elemtype *_type // 元素的類型(指向類型信息的指針)
sendx uint // 寫入元素的位置
recvx uint // 讀取元素的位置
recvq waitq // 等待接收的隊列(包含等待接收的 goroutine)
sendq waitq // 等待發送的隊列(包含等待發送的 goroutine)// lock 保護 hchan 中的所有字段,以及阻塞在這個通道上的 sudogs 中的幾個字段。
// 在持有此鎖時,不要更改另一個 G 的狀態(特別是不要使 G 變為可運行狀態),
// 因為這可能會與棧收縮操作發生死鎖。
lock mutex //互斥鎖
}
環形隊列是依靠數組實現的(buf指向該數組),實現方法類似雙指針:一個指向寫入位置(sendx),一個指向讀取位置(recvx)
等待隊列遵循先進先出,阻塞中的攜程會被相反的操作依次喚醒
如果寫入時,等待接收隊列非空(recvq),那么直接將數據給到等待的攜程,不用經過緩沖區
select可以監控單/多個管道內是否有數據,有就將其讀出;沒有也不會阻塞,直接返回;
select執行順序是隨機的
func main() {ch1 := make(chan int)ch2 := make(chan int)go write(ch1)go write(ch2)for {select {case e := <-ch1:fmt.Printf("ch1:%d\n", e)case e := <-ch2:fmt.Printf("ch2:%d\n", e)default:fmt.Println("none")time.Sleep(1 * time.Second)}}
}
func write(ch chan<- int) {for {ch <- 1time.Sleep(time.Second)}
}
for-range 讀取管道時,管道關閉之后不會繼續讀取管道內數據;
for 循環讀取管道時,管道關閉后,仍會繼續讀取管道內的數據,返回一堆 零值,false
func main() {ch1 := make(chan int)go write(ch1)//for e := range ch1 { // 關閉后不會再從管道讀取數據// fmt.Print(e)//}//1111for { // 關閉后仍在從管道讀取數據。返回 零值,falsefmt.Print(<-ch1)}//11110000000000000000000000000000000000000.....
}
func write(ch chan<- int) {for i := 1; i < 5; i++ {ch <- 1time.Sleep(time.Second)}close(ch)
}