iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 15
4
Software Development

Go繁不及備載系列 第 15

# Day15 給我Go通道- Golang Channel (Block vs Deadlock)

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`

由以下兩個例子來說明接、收方向性

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

【Buffered channel】

  • 有空間限制的通道(buffered channel)

Variable := make(chan Type, Number)

c := make(chan int, 2)

有限制儲存空間的通道,若限制放兩個,就只能有兩個充氣娃娃。
此時又塞第三個進去會爆炸!
喔說錯了,打結而已啦。

https://play.golang.org/p/AxO5Xd7tGrU

【Deadlock死結】

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

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 是需要有同時有 一頭寫入、另一頭讀出,才能動的。

那Golang有沒有 無限制的通道(Unlimited) 呢?

殘酷的答案是,沒有

至於為什麼沒有?
要先想想喔,如果給1000個Byte緩衝的通道,代表程式執行時要預先挪空出1000個Byte個空間。
那如果今天是1000000個呢、甚至無限個呢?這還能不爆炸嗎?

讀寫速率要控制在一定的範圍內,Channel中的緩衝區塊才能起到作用。
若寫入通道中的速度永遠大於寫入速度(塞娃娃的速度永遠比用娃娃來的快),那麼給再多再大的倉庫放,永遠都會有不夠放的一天。

但是若是實作上真的有需求,可以透過一些trick的手段達成、模擬無限通道這件事,
例如使用 slice 來記錄通道中的東西。


上一篇
# Day14 Go併發症狀- Goroutines (go)
下一篇
# Day16 我OK,你先GO (sync)
系列文
Go繁不及備載35
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
alan_jhu
iT邦新手 5 級 ‧ 2021-06-30 17:04:43

這個例子中,通道只能放5個,main sent 不是應該打印到4之後滿了(從0開始),接著打印fun got 0,從通道拉出一個數後,再打印5。怎麼會先打印到5呢?

ch := make(chan int, 5)

https://ithelp.ithome.com.tw/upload/images/20210630/20138929yZ7QT5jzpo.png

gjlmotea iT邦研究生 5 級 ‧ 2023-11-16 01:22:19 檢舉

通道只能塞五個沒錯

推測可能是時間太近了 幾乎同時,產生先取後印的結果

可以試試看這兩個範例
https://go.dev/play/p/UoTsTWbfnFt

https://go.dev/play/p/ChVE38s-ct8

我要留言

立即登入留言