# Day15 給我Go通道- Golang Channel (Block vs Deadlock) 昨天提到 因為`被併發出去的func`不會回傳值, 若想要在併發線程之間交流、傳遞資料、發送訊息, 就必須要,沒錯,就是地鼠們最熟悉的—— **徒手挖地道**。 ## 通道(Channel) * 通道分成兩種:**有Buffer** 跟 **無Buffer** 什麼意思?就是`有儲存空間限制的通道` vs `無儲存空間的通道` * 通道也能分成另外兩種:**單向** 跟 **雙向** 什麼意思? 就是`主動call方 (caller)` 與 `被call方 (callee)`都能傳資料 vs `只有一方`能傳資料 **通道預設是雙向的**,除非你狠心將它變成單向道。 另外,就像有些地窖只能放酒、有些放食物、有的只能放充氣娃娃(?) 地鼠們挖的通道,也是有`型別 (Type)` 之分的,建立通道時記得要加。 ### 【Unbuffered channel】 * 無緩衝的通道(unbuffered channel) > **Variable := make(chan Type)** >> c := make(chan int) `通道(chan)`製造出來之後,需要傳進`要被你併發出去的func`, 他靠這個傳資料的。 `chan`是有方向性的,要看**箭頭**`<-`的方向。 ``` chan <- A `把 A這個東西 塞進chan` B<- chan `從chan 挖東西出來 到B` ``` 由以下兩個例子來說明`接、收方向性`: ```go func main() { ch := make(chan int) go func1(ch) ch <- 100 } func func1(ch chan int) { i := <-ch fmt.Println(i) } ``` ```go 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 ### 【Buffered channel】 * 有空間限制的通道(buffered channel) > **Variable := make(chan Type, Number)** >> c := make(chan int, 2) 有限制儲存空間的通道,若限制放兩個,就只能有兩個充氣娃娃。 此時又塞第三個進去會爆炸! 喔說錯了,打結而已啦。 https://play.golang.org/p/AxO5Xd7tGrU #### 【Deadlock死結】 ```go 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! */ ``` ### 【阻塞Block vs 死結Deadlock】 通常chan塞不下第三個充氣娃娃時,只會發生`Block(阻塞滯留)`, 而當`Block`**永遠無法解開**的情況發生,則是 `Deadlock(死結)`。 上面會發生死結是因為 不論等多久,都不會從`Block`的狀態中脫離。 只要`通道(Chan)`**塞不下**,或者**沒東西可挖**,都會發生`Block`阻塞。 #### 【Block阻塞】 以下是`通道Channel` `阻塞Block`的例子 https://play.golang.org/p/9N-B6QFu1Q_v ```go 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` 會發生什麼事情? ```go 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 是需要有同時有 **一頭寫入、另一頭讀出**,才能動的。 ### 那Golang有沒有 **無限制的通道(Unlimited)** 呢? 殘酷的答案是,**沒有** 。 至於為什麼沒有? 要先想想喔,如果給1000個Byte緩衝的通道,代表程式執行時要預先挪空出1000個Byte個空間。 那如果今天是1000000個呢、甚至無限個呢?這還能不爆炸嗎? 讀寫速率要控制在一定的範圍內,Channel中的緩衝區塊才能起到作用。 若寫入通道中的速度永遠大於寫入速度(塞娃娃的速度永遠比用娃娃來的快),那麼給再多再大的倉庫放,永遠都會有不夠放的一天。 但是若是實作上真的有需求,可以透過一些trick的手段達成、模擬無限通道這件事, 例如使用 `slice` 來記錄通道中的東西。 或可參考 https://colobu.com/2021/05/11/unbounded-channel-in-go/