Golang-channel实现
一.簡介
? ? ? ?如果說goroutine是Go語言程序的并發體的話,那么channels則是它們之間的通信機制。一個channel是一個通信系統,它可以讓一個goroutine通過它給另一個goroutine發送值信息。每個channel都有一個特定的類型,也就是channels可發送數據的類型。
二.使用
2.1 創建channel
ch := make(chan int)2.2?channel讀寫
ch := make(chan int)// write to channel ch <- x// read from channel x <- ch// another way to read x = <- chchannel 一定要初始化后才能進行讀寫操作,否則會永久阻塞。
2.3 關閉 channel
ch := make(chan int)close(ch)有關 channel 的關閉,你需要注意以下事項:
- 關閉一個未初始化(nil) 的 channel 會產生 panic
- 重復關閉同一個 channel 會產生 panic
- 向一個已關閉的 channel 中發送消息會產生 panic
- 從已關閉的 channel 讀取消息不會產生 panic,且能讀出 channel 中還未被讀取的消息,若消息均已讀出,則會讀到類型的零值。從一個已關閉的 channel 中讀取消息永遠不會阻塞,并且會返回一個為 false 的 ok-idiom,可以用它來判斷 channel 是否關閉
- 關閉 channel 會產生一個廣播機制,所有向 channel 讀取消息的 goroutine 都會收到消息
三.channel的兩種類型
3.1 有緩沖channel
ch := make(chan int, 10)? ? ? ?有緩存的 channel 類似一個阻塞隊列(采用環形數組實現)。當緩存未滿時,向 channel 中發送消息時不會阻塞,當緩存滿時,發送操作將被阻塞,直到有其他 goroutine 從中讀取消息;相應的,當 channel 中消息不為空時,讀取消息不會出現阻塞,當 channel 為空時,讀取操作會造成阻塞,直到有 goroutine 向 channel 中寫入消息。
3.2 無緩沖channel
ch := make(chan int)? ? ? ?從無緩存的 channel 中讀取消息會阻塞,直到有 goroutine 向該 channel 中發送消息;同理,向無緩存的 channel 中發送消息也會阻塞,直到有 goroutine 從 channel 中讀取消息。
3.3??select 使用
select 用法類似 IO 多路復用,可以同時監聽多個 channel 的消息狀態,看下面的例子
select {case <- ch1:...case <- ch2:...case ch3 <- 10;...default:... }???????
msgCh := make(chan struct{}) quitCh := make(chan struct{}) for {select {case <- msgCh:doWork()case <- quitCh:finish()return }- select 可以同時監聽多個 channel 的寫入或讀取
- 執行 select 時,若只有一個 case 通過(不阻塞),則執行這個 case 塊
- 若有多個 case 通過,則隨機挑選一個 case 執行
- 若所有 case 均阻塞,且定義了 default 模塊,則執行 default 模塊。若未定義 default 模塊,則 select 語句阻塞,直到有 case 被喚醒。
- 使用 break 會跳出 select 塊。
三.底層實現
2.1 hchan結構
type hchan struct {qcount uint // 隊列中當前數據的個數dataqsiz uint // size of the circular queuebuf unsafe.Pointer // 數據緩沖區,存放數據的環形數組elemsize uint16 // channel中數據類型的大小(單個元素的大小)closed uint32 // 表示channel是否關閉標識位elemtype *_type // 隊列中的元素類型sendx uint // 當前發送元素的索引recvx uint // 當前接收元素的索引recvq waitq // 接受等待隊列,由recv行為(也就是<-ch)阻塞在channel上的goroutine隊列sendq waitq // 發送等待隊列, 由send行為(也就是ch<-)阻塞在channel上的goroutine隊列//lock保護chann中的所有字段,以及在此通道上阻塞的sudoG中的幾個字段。//保持此鎖時不要更改另一個G狀態(特別是沒準備好G),因為這可能會因堆棧收縮而死鎖lock mutex }//發送及接收隊列的·1結構體 type waitq struct {first *sudoglast *sudog }- qcount uint // 當前隊列中剩余元素個數。
- dataqsiz uint // 環形隊列長度,即緩沖區的大小,即make(chan T,N),N。
- buf unsafe.Pointer // 環形隊列指針。
- elemsize uint16 // 每個元素的大小。
- closed uint32 // 表示當前通道是否處于關閉狀態。創建通道后,該字段設置為0,即通道打開; 通過調用close將其設置為1,通道關閉。
- elemtype *_type // 元素類型,用于數據傳遞過程中的賦值。
- sendx uint 和?recvx uint是環形緩沖區的狀態字段,它指示緩沖區的當前索引 - 支持數組,它可以從中發送數據和接收數據。
- recvq waitq // 等待讀消息的goroutine隊列。
- sendq waitq // 等待寫消息的goroutine隊列。
- lock mutex // 互斥鎖,為每個讀寫操作鎖定通道,因為發送和接收必須是互斥操作。
2.2 創建過程
2.2.1 寫入操作
1.創建帶buffer的channel
2.向channel中寫入數據
3.3 寫入過程如下:
- 鎖定整個管道結構。
- 確定寫入,嘗試從等會帶隊列等待goroutine,然后將元素直接寫入goroutine。
- 如果recvq為空,則確定緩沖區是否可用。如果可用,從當前goroutine復制數據到緩沖區。
- 如果緩沖區已滿,則要寫入的元素將保存在當前正在執行的goroutine結構中,并且當前goroutine將在sendq中排隊并從運行中掛起。
- 寫入完成釋放鎖。
2.2.2 讀取過程
- 先讀取channel全局鎖。
- 嘗試sendq從等待隊列中獲取等待的goroutine。
- 如果有等待的goroutine,且有緩沖區(緩沖區已滿),從緩沖區隊首取出數據,再從sendq取出一個goroutine。將goroutine中數據存入buf對位,結束讀取釋放鎖。
- 如沒有后等待的goroutine,且緩沖區有數據,直接讀取緩沖區數據,解釋讀取釋放鎖。
- 如果沒有等待的goroutine,且沒有緩沖或緩沖區域為空,將當前的goroutine加入denq排隊,進入睡眠,等待被寫goroutine喚醒。結束釋放鎖。
總結
以上是生活随笔為你收集整理的Golang-channel实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: maven项目查询jar依赖的网址
- 下一篇: 记一次confluence故障的RCA