mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2024-12-25 14:28:58 +00:00
ch8-04-2 done
This commit is contained in:
parent
59aef265f5
commit
2d17bf5891
@ -1,10 +1,10 @@
|
||||
### 8.4.1. 不帶緩存的Channels
|
||||
|
||||
一個基於無緩存Channels的發送操作將導致發送者goroutine阻塞,直到另一個goroutine在相同的Channels上執行接收操作,當發送的值通過Channels成功傳輸之後,兩個goroutine可以繼續執行後面的語句。反之,如果接收操作先發送,那麽接收者goroutine也將阻塞,直到有另一個goroutine在相同的Channels上執行發送操作。
|
||||
一個基於無緩存Channels的發送操作將導致發送者goroutine阻塞,直到另一個goroutine在相同的Channels上執行接收操作,當發送的值通過Channels成功傳輸之後,兩個goroutine可以繼續執行後面的語句。反之,如果接收操作先發生,那麽接收者goroutine也將阻塞,直到有另一個goroutine在相同的Channels上執行發送操作。
|
||||
|
||||
基於無緩存Channels的發送和接收操作將導致兩個goroutine做一次同步操作。因爲這個原因,無緩存Channels有時候也被稱爲同步Channels。當通過一個無緩存Channels發送數據時,接收者收到數據發生在喚醒發送者goroutine之前(譯註:*happens before*,這是Go語言併發內存模型的一個關鍵術語!)。
|
||||
|
||||
在討論併發編程時,當我們説x事件在y事件之前發生(*happens before*),我們併不是説x事件在時間上比y時間更早;我們要表達的意思是要保證在此之前事件都已經完成了,例如在此之前的更新某些變量的操作已經完成,你可以放心依賴這些保證了。
|
||||
在討論併發編程時,當我們説x事件在y事件之前發生(*happens before*),我們併不是説x事件在時間上比y時間更早;我們要表達的意思是要保證在此之前的事件都已經完成了,例如在此之前的更新某些變量的操作已經完成,你可以放心依賴這些已完成的事件了。
|
||||
|
||||
當我們説x事件旣不是在y事件之前發生也不是在y事件之後發生,我們就説x事件和y事件是併發的。這併不是意味着x事件和y事件就一定是同時發生的,我們隻是不能確定這兩個事件發生的先後順序。在下一章中我們將看到,當兩個goroutine併發訪問了相同的變量時,我們有必要保證某些事件的執行順序,以避免出現某些併發問題。
|
||||
|
||||
@ -35,5 +35,5 @@ func main() {
|
||||
|
||||
基於channels發送消息有兩個重要方面。首先每個消息都有一個值,但是有時候通訊的事實和發生的時刻也同樣重要。當我們更希望強調通訊發生的時刻時,我們將它稱爲**消息事件**。有些消息事件併不攜帶額外的信息,它僅僅是用作兩個goroutine之間的同步,這時候我們可以用`struct{}`空結構體作爲channels元素的類型,雖然也可以使用bool或int類型實現同樣的功能,`done <- 1`語句也比`done <- struct{}{}`更短。
|
||||
|
||||
**練習 8.3:** 在netcat3例子中,conn雖然是一個interface類型的值,但是其底層眞實類型是`*net.TCPConn`,代表一個TCP鏈接。一個TCP鏈接有讀和寫兩個部分,可以使用CloseRead和CloseWrite方法分别關閉它們。脩改netcat3的主goroutine代碼,隻關閉網絡鏈接中寫的部分,這樣的話後台goroutine可以在標準輸入被關閉後繼續打印從reverb1服務器傳迴的數據。(要在reverb2服務器也完成同樣的功能是比較睏難的;參考**練習 8.4:**)
|
||||
**練習 8.3:** 在netcat3例子中,conn雖然是一個interface類型的值,但是其底層眞實類型是`*net.TCPConn`,代表一個TCP鏈接。一個TCP鏈接有讀和寫兩個部分,可以使用CloseRead和CloseWrite方法分别關閉它們。脩改netcat3的主goroutine代碼,隻關閉網絡鏈接中寫的部分,這樣的話後台goroutine可以在標準輸入被關閉後繼續打印從reverb1服務器傳迴的數據。(要在reverb2服務器也完成同樣的功能是比較睏難的;參考**練習 8.4**。)
|
||||
|
||||
|
103
ch8/ch8-04-2.md
103
ch8/ch8-04-2.md
@ -1,3 +1,102 @@
|
||||
### 8.4.2. 管道(Pipeline)
|
||||
### 8.4.2. 串聯的Channels(Pipeline)
|
||||
|
||||
Channels也可以用於將多個goroutine鏈接在一起,一個Channels的輸出作爲下一個Channels的輸入。這種串聯的Channels就是所謂的管道(pipeline)。下面的程序用兩個channels將三個goroutine串聯起來,如圖8.1所示。
|
||||
|
||||
![](../images/ch8-01.png)
|
||||
|
||||
第一個goroutine是一個計數器,用於生成0、1、2、……形式的整數序列,然後通過channel將該整數序列發送給第二個goroutine;第二個goroutine是一個求平方的程序,對收到的每個整數求平方,然後將平方後的結果通過第二個channel發送給第三個goroutine;第三個goroutine是一個打印程序,打印收到的每個整數。爲了保持例子清晰,我們有意選擇了非常簡單的函數,當然三個goroutine的計算很簡單,在現實中確實沒有必要爲如此簡單的運算構建三個goroutine。
|
||||
|
||||
```Go
|
||||
gopl.io/ch8/pipeline1
|
||||
|
||||
func main() {
|
||||
naturals := make(chan int)
|
||||
squares := make(chan int)
|
||||
|
||||
// Counter
|
||||
go func() {
|
||||
for x := 0; ; x++ {
|
||||
naturals <- x
|
||||
}
|
||||
}()
|
||||
|
||||
// Squarer
|
||||
go func() {
|
||||
for {
|
||||
x := <-naturals
|
||||
squares <- x * x
|
||||
}
|
||||
}()
|
||||
|
||||
// Printer (in main goroutine)
|
||||
for {
|
||||
fmt.Println(<-squares)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
如您所料,上面的程序將生成0、1、4、9、……形式的無窮數列。像這樣的串聯Channels的管道(Pipelines)可以用在需要長時間運行的服務中,每個長時間運行的goroutine可能會包含一個死循環,在不同goroutine的死循環內部使用串聯的Channels來通信。但是,如果我們希望通過Channels隻發送有限的數列該如何處理呢?
|
||||
|
||||
如果發送者知道,沒有更多的值需要發送到channel的話,那麽讓接收者也能及時知道沒有多餘的值可接收將是有用的,因爲接收者可以停止不必要的接收等待。這可以通過內置的close函數來關閉channel實現:
|
||||
|
||||
```Go
|
||||
close(naturals)
|
||||
```
|
||||
|
||||
當一個channel被關閉後,再向該channel發送數據將導致panic異常。當一個被關閉的channel中已經發送的數據都被成功接收後,後續的接收操作將不再阻塞,它們會立卽返迴一個零值。關閉上面例子中的naturals變量對應的channel併不能終止循環,它依然會收到一個永無休止的零值序列,然後將它們發送給打印者goroutine。
|
||||
|
||||
沒有辦法直接測試一個channel是否被關閉,但是接收操作有一個變體形式:它多接收一個結果,多接收的第二個結果是一個布爾值ok,ture表示成功從channels接收到值,false表示channels已經被關閉併且里面沒有值可接收。使用這個特性,我們可以脩改squarer函數中的循環代碼,當naturals對應的channel被關閉併沒有值可接收時跳出循環,併且也關閉squares對應的channel.
|
||||
|
||||
```Go
|
||||
// Squarer
|
||||
go func() {
|
||||
for {
|
||||
x, ok := <-naturals
|
||||
if !ok {
|
||||
break // channel was closed and drained
|
||||
}
|
||||
squares <- x * x
|
||||
}
|
||||
close(squares)
|
||||
}()
|
||||
```
|
||||
|
||||
因爲上面的語法是笨拙的,而且這種處理模式很場景,因此Go語言的range循環可直接在channels上面迭代。使用range循環是上面處理模式的簡潔語法,它依次從channel接收數據,當channel被關閉併且沒有值可接收時跳出循環。
|
||||
|
||||
在下面的改進中,我們的計數器goroutine隻生成100個含數字的序列,然後關閉naturals對應的channel,這將導致計算平方數的squarer對應的goroutine可以正常終止循環併關閉squares對應的channel。(在一個更複雜的程序中,可以通過defer語句關閉對應的channel。)最後,主goroutine也可以正常終止循環併退出程序。
|
||||
|
||||
```Go
|
||||
gopl.io/ch8/pipeline2
|
||||
|
||||
func main() {
|
||||
naturals := make(chan int)
|
||||
squares := make(chan int)
|
||||
|
||||
// Counter
|
||||
go func() {
|
||||
for x := 0; x < 100; x++ {
|
||||
naturals <- x
|
||||
}
|
||||
close(naturals)
|
||||
}()
|
||||
|
||||
// Squarer
|
||||
go func() {
|
||||
for x := range naturals {
|
||||
squares <- x * x
|
||||
}
|
||||
close(squares)
|
||||
}()
|
||||
|
||||
// Printer (in main goroutine)
|
||||
for x := range squares {
|
||||
fmt.Println(x)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
其實你併不需要關閉每一個channel。隻要當需要告訴接收者goroutine,所有的數據已經全部發送時才需要關閉channel。不管一個channel是否被關閉,當它沒有被引用時將會被Go語言的垃圾自動迴收器迴收。(不要將關閉一個打開文件的操作和關閉一個channel操作混淆。對於每個打開的文件,都需要在不使用的使用調用對應的Close方法來關閉文件。)
|
||||
|
||||
視圖重複關閉一個channel將導致panic異常,視圖關閉一個nil值的channel也將導致panic異常。關閉一個channels還會觸發一個廣播機製,我們將在8.9節討論。
|
||||
|
||||
|
||||
TODO
|
||||
|
Loading…
Reference in New Issue
Block a user