無緩衝通道
是指在接收前沒有能力儲存任何值得通道。
這種型別的通道要求傳送goroutine和接收goroutine同時準備好,才能完成傳送和接收操作。如果兩個goroutine沒有同時準備好,通道會導致先執行傳送或接收操作的goroutine阻塞等待。
這種對通道進行傳送和接收的互動行為本身就是同步的,其中任意一個操作都無法離開另一個操作單獨存在。
上圖所示,如同接力賽。根據圖編號觀察①兩個協程,建立好了通道②一個往通道里放,這時候兩邊阻塞④這時候另一個協程要接⑤另一個協程取出來,從①-⑤都是阻塞的,⑥才完成交接,才不會阻塞。
再比喻: 就是一個送信人去你家門口送信 ,你不在家 他不走,你一定要接下信,他才會走。
無緩衝channel建立
ch := make(chan int, 0) //第二個引數為0,或者不寫第二個引數
如果沒有指定緩衝區容量,那麼該通道就是同步的,因此會阻塞到傳送者準備好傳送和接收者準備好接收。
程式碼案例
package main
import (
"fmt"
"time"
)
func main() {
//建立一個無快取的channel
ch := make(chan int, 0)
//len(ch)緩衝區剩餘資料個數, cap(ch)緩衝區大小,兩者這裡永遠都是0
fmt.Printf("len(ch) = %d, cap(ch)= %d\n", len(ch), cap(ch))
//新建協程
go func() {
for i := 0; i < 3; i++ { //寫三次
fmt.Printf("子協程:i = %d\n", i)
ch <- i //往chan寫內容
fmt.Printf("len(ch) = %d, cap(ch)= %d\n", len(ch), cap(ch))
}
}()
//延時2秒
time.Sleep(2 * time.Second)
for i := 0; i < 3; i++ { //必須讀三次
num := <-ch //讀管道中內容,沒有內容前,阻塞
fmt.Println("num = ", num)
}
}
len(ch) = 0, cap(ch)= 0
子協程:i = 0
num = 0
len(ch) = 0, cap(ch)= 0
子協程:i = 1
len(ch) = 0, cap(ch)= 0
子協程:i = 2
num = 1
num = 2
流程分析
主協程執行到這裡,阻塞兩秒
//延時2秒
time.Sleep(2 * time.Second)
//兩秒鐘時間子協程肯定把for迴圈執行完畢,但這裡也會出現阻塞
//阻塞原因是:當執行到往通道寫資料是無緩衝的,對方不讀之前會阻塞。也就是,在主協程等著子協程寫完,但是主協程還沒到讀的時候,這時候出現阻塞,等到主協程讀完資料才會往下走。
可以執行觀察一下程式的執行卡頓觀察阻塞
for i := 0; i < 3; i++ { //寫三次
fmt.Printf("子協程:i = %d\n", i)
ch <- i //往chan寫內容
fmt.Printf("len(ch) = %d, cap(ch)= %d\n", len(ch), cap(ch))
}
列印結果分析:首先子協程for迴圈往管道里寫入一個資料,緊接著主協程for迴圈出現阻塞,然後主協程for迴圈從管道讀資料,讀完了列印。主協程列印完,子協程for迴圈繼續給管道資料,但也有可能主協程讀完資料沒來得及列印,子協程就把資料寫入管道並列印完畢,因為兩個是同時並行的。
有緩衝通道
指通道可以儲存多個值。
如果給定了一個緩衝區容量,那麼通道就是非同步的,只要緩衝區有未使用空間用於傳送資料,或還包含可以接收的資料,那麼其通訊就會無阻塞地進行
上圖所示:
①右側的goroutine正在從通道接收一個值。
②右側的goroutine獨立完成了接手值得動作,而左側的goroutine正在傳送一個新值到通道里。
③左側的goroutine還在向通道傳送新值,而右側的goroutine正在從通道接收另一個值。這個步驟裡的兩個操作既不是同步,也不會互相阻塞。
④所有的傳送和接收都完成,而通道里還有幾個值,也有一些空間可以儲存更多的值
有緩衝channel建立
ch := make(chan int, 3) //容量是3
程式碼案例
package main
import (
"fmt"
"time"
)
func main() {
//建立一個有快取的channel
ch := make(chan int, 3) //容量是3
//len(ch)緩衝區剩餘資料個數, cap(ch)緩衝區大小
fmt.Printf("len(ch) = %d, cap(ch)= %d\n", len(ch), cap(ch))
//新建協程
go func() {
for i := 0; i < 10; i++ { //這裡資料量大於管道容量,會出阻塞
ch <- i //往chan寫內容,如果主協程沒讀的話,寫滿3個就會阻塞在此
fmt.Printf("子協程[%d]: len(ch) = %d, cap(ch)= %d\n", i, len(ch), cap(ch))
}
}()
//延時
time.Sleep(2 * time.Second)
for i := 0; i < 10; i++ { //這裡資料量大於管道容量,會出阻塞
num := <-ch //讀管道中內容,沒有內容前,阻塞
fmt.Println("num = ", num)
}
}
len(ch) = 0, cap(ch)= 3
子協程[0]: len(ch) = 1, cap(ch)= 3
子協程[1]: len(ch) = 2, cap(ch)= 3
子協程[2]: len(ch) = 3, cap(ch)= 3
num = 0
num = 1
num = 2
num = 3
子協程[3]: len(ch) = 3, cap(ch)= 3
子協程[4]: len(ch) = 0, cap(ch)= 3
子協程[5]: len(ch) = 1, cap(ch)= 3
子協程[6]: len(ch) = 2, cap(ch)= 3
子協程[7]: len(ch) = 3, cap(ch)= 3
num = 4
num = 5
num = 6
num = 7
num = 8
子協程[8]: len(ch) = 3, cap(ch)= 3
子協程[9]: len(ch) = 0, cap(ch)= 3
num = 9
總結一下有緩衝channel和無緩衝channel的特點與不同
無緩衝的與有緩衝channel有著重大差別,那就是一個是同步的 一個是非同步的。
比如
c1:=make(chan int) 無緩衝
c2:=make(chan int,1) 有緩衝
c1<-1
無緩衝: 不僅僅是向 c1 通道放 1,而是一直要等有別的攜程 <-c1 接手了這個引數,那麼c1<-1才會繼續下去,要不然就一直阻塞著。
有緩衝: c2<-1 則不會阻塞,因為緩衝大小是1(其實是緩衝大小為0),只有當放第二個值的時候,第一個還沒被人拿走,這時候才會阻塞。