昨天提到 因為被併發出去的func
不會回傳值,
若想要在併發線程之間交流、傳遞資料、發送訊息,
就必須要,沒錯,就是地鼠們最熟悉的——
徒手挖地道。
通道分成兩種:有Buffer 跟 無Buffer
什麼意思?就是有儲存空間限制的通道
vs 無限制的通道
通道也能分成另外兩種:單向 跟 雙向
什麼意思?
就是主動call方 (caller)
與 被call方 (callee)
都能傳資料 vs 只有一方
能傳資料
通道預設是雙向的,除非你狠心將它變成單向道。
另外,就像有些地窖只能放酒、有些放食物、有的只能放充氣娃娃(?)
地鼠們挖的通道,也是有型別 (Type)
之分的,建立通道時記得要加。
Variable := make(chan Type)
c := make(chan int)
通道(chan)
製造出來之後,需要傳進要被你併發出去的func
,
他靠這個傳資料的。
chan
是有方向性的,要看箭頭<-
的方向。
chan <- A `把 A這個東西 塞進chan`
B<- chan `從chan 挖東西出來 到B`
由以下兩個例子來說明接、收方向性
:
func main() {
ch := make(chan int)
go func1(ch)
ch <- 100
}
func func1(ch chan int) {
i := <-ch
fmt.Println(i)
}
func main() {
ch := make(chan int)
go func2(ch)
got := <-ch
fmt.Println(got)
}
func func2(ch chan int) {
time.Sleep(time.Second * 2)
ch <- 999
}
https://play.golang.org/p/KgZXzvNr4iP
Variable := make(chan Type, Number)
c := make(chan int, 2)
有限制儲存空間的通道,若限制放兩個,就只能有兩個充氣娃娃。
此時又塞第三個進去會爆炸!
喔說錯了,打結而已啦。
https://play.golang.org/p/AxO5Xd7tGrU
func main() {
ch := make(chan int, 2)
go func3(ch)
ch <- 100
ch <- 99
ch <- 98 // 發生deadlock
}
func func3(ch chan int) {
}
/* result:
fatal error: all goroutines are asleep - deadlock!
*/
通常chan塞不下第三個充氣娃娃時,只會發生Block(阻塞滯留)
,
而當Block
永遠無法解開的情況發生,則是 Deadlock(死結)
。
上面會發生死結是因為 不論等多久,都不會從Block
的狀態中脫離。
只要通道(Chan)
塞不下,或者沒東西可挖,都會發生Block
阻塞。
以下是通道Channel
阻塞Block
的例子
https://play.golang.org/p/9N-B6QFu1Q_v
func main() {
ch := make(chan int, 2)
go func4(ch)
for i := 0; i < 10; i++ {
ch <- i
fmt.Println("main sent", i)
}
time.Sleep(time.Second)
}
func func4(ch chan int) {
for {
i := <-ch
fmt.Println("func got", i)
time.Sleep(time.Millisecond * 100)
}
}
/* result:
func got 0
main sent 0
main sent 1
main sent 2
func got 1
...
...
*/
主程式不間斷地連續塞十次數字
送完休息1秒;而func4
每0.1秒吃下來一個數值。
雖然慢,但程式不會打死結
,慢慢執行終有一天能找到屬於他的出路。
Go還真勵志。
如果把Buffer Size: 2
換成5
會發生什麼事情?
ch := make(chan int, 5)
同時間通道裡最多會有五個數字。
塞與取的先後順序,透過log.SetFlags(5)
來看會比較清楚。
https://play.golang.org/p/MzL_-cj0N6h
小坑注意:
初學時容易搞混,無緩衝通道(Unbuffered) 並不等於 **無限制(Unlimited)**的通道。
The buffer size is the number of elements that can be sent to the channel without the send blocking.
Buffer 是拿來緩衝用的,Unbuffered Channel則是0緩衝
,就是沒有緩衝啦!
Unbuffered 是需要有同時有 一頭寫入、另一頭讀出,才能動的。
殘酷的答案是,沒有 。
至於為什麼沒有?
要先想想喔,如果給1000個Byte緩衝的通道,代表程式執行時要預先挪空出1000個Byte個空間。
那如果今天是1000000個呢、甚至無限個呢?這還能不爆炸嗎?
讀寫速率要控制在一定的範圍內,Channel中的緩衝區塊才能起到作用。
若寫入通道中的速度永遠大於寫入速度(塞娃娃的速度永遠比用娃娃來的快),那麼給再多再大的倉庫放,永遠都會有不夠放的一天。
但是若是實作上真的有需求,可以透過一些trick的手段達成、模擬無限通道這件事,
例如使用 slice
來記錄通道中的東西。
這個例子中,通道只能放5個,main sent 不是應該打印到4之後滿了(從0開始),接著打印fun got 0,從通道拉出一個數後,再打印5。怎麼會先打印到5呢?
ch := make(chan int, 5)