- Go語言高級編程(第2版)
- 柴樹杉 曹春暉
- 1078字
- 2025-08-07 17:56:16
1.5.6 基于通道的通信
通道(channel)是在goroutine之間進行同步的主要方法。在無緩存的通道上的每一次發送操作都有與其對應的接收操作,發送操作和接收操作通常發生在不同的goroutine中(在同一個goroutine中執行兩個操作很容易導致死鎖)。無緩存的通道上的發送操作總在對應的接收操作完成前發?生。
var done = make(chan bool) var msg string func aGoroutine() { msg = "你好, 世界" done <- true } func main() { go aGoroutine() <-done println(msg) }
以上程序可保證打印出“你好, 世界”。該程序首先對msg
進行寫入,然后在done
通道上發送同步信號,隨后從done
接收對應的同步信號,最后執行println
()
函?數。
若在關閉通道后繼續從中接收數據,接收者就會收到該通道返回的零值。因此在這個例子中,用close(done)
關閉通道代替done <- true
依然能保證該程序產生相同的行?為。
var done = make(chan bool) var msg string func aGoroutine() { msg = "你好, 世界" close(done) } func main() { go aGoroutine() <-done println(msg) }
根據Go語言內存模型規范,對于從無緩存的通道進行的接收,發生在對該通道進行的發送完成之前。基于上面這個規則可知,交換兩個goroutine中的接收操作和發送操作也是可以的(但是很危險):
var done = make(chan bool) var msg string func aGoroutine() { msg = "hello, world" <-done } func main() { go aGoroutine() done <- true println(msg) }
這樣也可保證打印出“hello, world”。因為main
線程中done <- true
發送完成前后臺線程<-done
接收已經開始(這保證msg = "hello, world"
被執行了),所以之后println
(msg)
的msg
已經被賦過值了。簡而言之,后臺線程首先對msg
進行寫入,然后從done
中接收信號,隨后main
線程向done
發送對應的信號,最后執行println
()
函數完成。但是,若該通道為帶緩存的(如done = make(chan bool, 1)
),main
線程的done <- true
接收操作將不會被后臺線程的<-done
接收操作阻塞,該程序將無法保證打印出“hello, world”。
對于帶緩存的通道,對通道中的第K個接收操作發生在第K+C個發送操作完成之前,其中C是通道的緩存大小。如果將C
設置為0,自然就對應無緩存的通道,也就是第K個接收操作在第K個發送操作完成之前。因為無緩存的通道只能同步發1個,所以也就簡化為前面無緩存通道的規則:對于從無緩存的通道進行的接收,發生在對該通道進行的發送完成之?前。
我們可以根據控制通道的緩存大小來控制并發執行的goroutine的最大數目,例如:
var limit = make(chan int, 3) var work = []func(){ func() { println("1"); time.Sleep(1 * time.Second) }, func() { println("2"); time.Sleep(1 * time.Second) }, func() { println("3"); time.Sleep(1 * time.Second) }, func() { println("4"); time.Sleep(1 * time.Second) }, func() { println("5"); time.Sleep(1 * time.Second) }, } func main() { for _, w := range work { go func(w func()) { limit <- 1 w() <-limit }(w) } select{} }
在循環創建goroutine過程中,使用了匿名函數并在函數中引用了循環變量w
,由于w
是傳引用的而非傳值的,因此無法保證goroutine在運行時調用的w
與循環創建時的w
是同一個值。為了解決這個問題,可以利用函數傳遞參數的值副本來為每個goroutine單獨復制一份w
。
最后的select{}
是一個空的通道選擇語句,該語句會導致main
線程阻塞,從而避免程序過早退出。還有for{}
、<-make(chan int)
等諸多方法可以達到類似的效果。因為main
線程被阻塞了,如果需要程序正常退出,可以調用os.Exit(0)
。