14.3 協程的同步:關閉通道-測試阻塞的通道
通道可以被顯式的關閉;儘管它們和文件不同:不必每次都關閉。只有在當需要告訴接收者不會再提供新的值的時候,才需要關閉通道。只有發送者需要關閉通道,接收者永遠不會需要。
繼續看示例 goroutine2.go(示例 14.2):我們如何在通道的 sendData()
完成的時候發送一個信號,getData()
又如何檢測到通道是否關閉或阻塞?
第一個可以通過函數 close(ch)
來完成:這個將通道標記爲無法通過發送操作 <-
接受更多的值;給已經關閉的通道發送或者再次關閉都會導致運行時的 panic。在創建一個通道後使用 defer 語句是個不錯的辦法(類似這種情況):
ch := make(chan float64)
defer close(ch)
第二個問題可以使用逗號,ok 操作符:用來檢測通道是否被關閉。
如何來檢測可以收到沒有被阻塞(或者通道沒有被關閉)?
v, ok := <-ch // ok is true if v received value
通常和 if 語句一起使用:
if v, ok := <-ch; ok {
process(v)
}
或者在 for 循環中接收的時候,當關閉或者阻塞的時候使用 break:
v, ok := <-ch
if !ok {
break
}
process(v)
可以通過 _ = ch <- v
來實現非阻塞發送,因爲空標識符獲取到了發送給 ch
的任何東西。在示例程序 14.2 中使用這些可以改進爲版本 goroutine3.go,輸出相同。
實現非阻塞通道的讀取,需要使用 select(參見第 14.4 節)。
示例 14.9-goroutine3.go:
package main
import "fmt"
func main() {
ch := make(chan string)
go sendData(ch)
getData(ch)
}
func sendData(ch chan string) {
ch <- "Washington"
ch <- "Tripoli"
ch <- "London"
ch <- "Beijing"
ch <- "Tokio"
close(ch)
}
func getData(ch chan string) {
for {
input, open := <-ch
if !open {
break
}
fmt.Printf("%s ", input)
}
}
改變了以下代碼:
- 現在只有
sendData()
是協程,getData()
和main()
在同一個線程中:
go sendData(ch)
getData(ch)
- 在
sendData()
函數的最後,關閉了通道:
func sendData(ch chan string) {
ch <- "Washington"
ch <- "Tripoli"
ch <- "London"
ch <- "Beijing"
ch <- "Tokio"
close(ch)
}
- 在 for 循環的
getData()
中,在每次接收通道的數據之前都使用if !open
來檢測:
for {
input, open := <-ch
if !open {
break
}
fmt.Printf("%s ", input)
}
使用 for-range 語句來讀取通道是更好的辦法,因爲這會自動檢測通道是否關閉:
for input := range ch {
process(input)
}
阻塞和生產者-消費者模式:
在第 14.2.10 節的通道迭代器中,兩個協程經常是一個阻塞另外一個。如果程序工作在多核心的機器上,大部分時間只用到了一個處理器。可以通過使用帶緩衝(緩衝空間大於 0)的通道來改善。比如,緩衝大小爲 100,迭代器在阻塞之前,至少可以從容器獲得 100 個元素。如果消費者協程在獨立的內核運行,就有可能讓協程不會出現阻塞。
由於容器中元素的數量通常是已知的,需要讓通道有足夠的容量放置所有的元素。這樣,迭代器就不會阻塞(儘管消費者協程仍然可能阻塞)。然後,這樣有效的加倍了迭代容器所需要的內存使用量,所以通道的容量需要限制一下最大值。記錄運行時間和性能測試可以幫助你找到最小的緩存容量帶來最好的性能。
鏈接
- 目錄
- 上一節:協程間的信道
- 下一節:使用 select 切換協程