Go管道阻塞、常見用法以及底層實作
- channel資料結構
- 一、管道狀態
- 二、阻塞
- 2.1 什么時候發生阻塞?
- 2.2 阻塞的本質?
- 三、常見錯誤
- 四、管道常見用法
- 4.1 單向管道
- 4.2 select
- 4.3 for-range
channel資料結構
type hchan struct {
qcount uint //當前元素個數(len)
dataqsiz uint //佇列長度(cap)
buf unsafe.Pointer //佇列指標,指向佇列記憶體位置
elemsize uint16 //單個元素大小
closed uint32 //標識通道狀態
elemtype *_type //元素型別
sendx uint //下一個元素存放的位置
recvx uint //下一個元素讀取的位置
recvq waitq //等待讀訊息的協程佇列
sendq waitq //等待發訊息的協程佇列
lock mutex //互斥鎖,保證不存在并發讀寫管道
//可以在GOROOT/src/runtime/chan.go里看到原始碼
}
一、管道狀態
通過closed欄位可知我們可以對管道是否關閉進行判斷:
var ch=make(chan int)
go func() {
for i := 0; i < 5; i++ {
ch<-i
}
close(ch)
}()
for {
if v, ok := <-ch; ok { //v,ok:=<-ch,只有當管道關閉且資料全部讀出時才回傳false
fmt.Println(v)
}else {
break
}
}
二、阻塞
2.1 什么時候發生阻塞?
- 向一個值為nil的管道寫或讀資料
- 無緩沖區時單獨的寫或讀資料
- 緩沖區為空時進行讀資料
- 緩沖區滿時進行寫資料
2.2 阻塞的本質?
每次發生阻塞管道都會將發生阻塞所在的協程送入recvq或sendq佇列
var ch=make(chan int,1)
for i:=0;i<3;i++ {
//每次回圈開啟協程Gi
go func(i int) {
ch<-i
}(i) //此時sendq=佇列:[G1,G2]
}
<-ch //此時sendq佇列:[G2]
<-ch //此時sendq佇列:[]
recvq佇列同理
另外main函式也可以視為一個協程,當main函式阻塞時如果沒有其他協程還在運作,就會發生死鎖
三、常見錯誤
- 關閉值為nil的管道
- 關閉已經關閉的管道
- 往已經close的管道發送資料
四、管道常見用法
4.1 單向管道
//只讀管道
func readChan(ch chan int) <-chan int {
return ch
}
//只寫管道
func writeChan(ch chan int) chan<- int {
return ch
}
可以增強資料安全性
4.2 select
var ch1 = make(chan int)
var ch2 = make(chan int)
select {
case <-ch1:
fmt.Println(1)
case <-ch2:
fmt.Println(2)
default:
fmt.Println(3)
}
select可以監控多個管道,當某個管道可操作時就會觸發相應的case分支,
4.3 for-range
for v:=range ch{
Do(...)
}
完全等同于:
for {
if v,ok:=<-ch;!ok{ //當close(ch)且ch內資料全部讀出時,ok==false
break
}else{
Do(...)
}
}
由此可見如果不主動close掉ch,讀出ch全部資料后該協程就會阻塞(<-ch),如果該for-range寫在main函式內,就會有死鎖的危險,
轉載請註明出處,本文鏈接:https://www.uj5u.com/qukuanlian/266424.html
標籤:區塊鏈
上一篇:GO語言學習—基礎概念
