iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 15
4
Software Development

Go繁不及備載系列 第 15 篇

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

  • 分享至 

  • xImage
  •  

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邦研究生 4 級 ‧ 2023-11-16 01:22:19 檢舉

通道只能塞五個沒錯

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

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

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

我要留言

立即登入留言