mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2025-12-17 19:24:19 +08:00
fmr code
This commit is contained in:
@@ -41,9 +41,11 @@ func fib(x int) int {
|
||||
```
|
||||
|
||||
動畵顯示了幾秒之後,fib(45)的調用成功地返迴,併且打印結果:
|
||||
|
||||
```
|
||||
Fibonacci(45) = 1134903170
|
||||
```
|
||||
|
||||
然後主函數返迴。當主函數返迴時,所有的goroutine都會直接打斷,程序退出。除了從主函數退出或者直接退出程序之外,沒有其它的編程方法能夠讓一個goroutine來打斷另一個的執行,但是我們之後可以看到,可以通過goroutine之間的通信來讓一個goroutine請求請求其它的goroutine,併讓其自己結束執行。
|
||||
|
||||
註意這里的兩個獨立的單元是如何進行組合的,spinning和菲波那契的計算。每一個都是寫在獨立的函數中,但是每一個函數都會併發地執行。
|
||||
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
網絡編程是併發大顯身手的一個領域,由於服務器是最典型的需要同時處理很多連接的程序,這些連接一般來自遠彼此獨立的客戶端。在本小節中,我們會講解go語言的net包,這個包提供編寫一個網絡客戶端或者服務器程序的基本組件,無論兩者間通信是使用TCP,UDP或者Unix domain sockets。在第一章中我們已經使用過的net/http包里的方法,也算是net包的一部分。
|
||||
|
||||
我們的第一個例子是一個順序執行的時鐘服務器,它會每隔一秒鐘將當前時間寫到客戶端:
|
||||
|
||||
```go
|
||||
gopl.io/ch8/clock1
|
||||
// Clock1 is a TCP server that periodically writes the time.
|
||||
@@ -92,6 +93,7 @@ func mustCopy(dst io.Writer, src io.Reader) {
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
這個程序會從連接中讀取數據,併將讀到的內容寫到標準輸出中,直到遇到end of file的條件或者發生錯誤。mustCopy這個函數我們在本節的幾個例子中都會用到。讓我們同時運行兩個客戶端來進行一個測試,這里可以開兩個終端窗口,下面左邊的是其中的一個的輸出,右邊的是另一個的輸出:
|
||||
|
||||
```
|
||||
@@ -124,6 +126,7 @@ for {
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
現在多個客戶端可以同時接收到時間了:
|
||||
|
||||
```
|
||||
|
||||
@@ -8,6 +8,7 @@ func handleConn(c net.Conn) {
|
||||
c.Close()
|
||||
}
|
||||
```
|
||||
|
||||
一個更有意思的echo服務應該模擬一個實際的echo的“迴響”,併且一開始要用大寫HELLO來表示“聲音很大”,之後經過一小段延遲返迴一個有所緩和的Hello,然後一個全小寫字母的hello表示聲音漸漸變小直至消失,像下面這個版本的handleConn(譯註:笑看作者腦洞大開):
|
||||
|
||||
```go
|
||||
|
||||
@@ -56,6 +56,7 @@ https://golang.org/blog/
|
||||
too many open files
|
||||
...
|
||||
```
|
||||
|
||||
最初的錯誤信息是一個讓人莫名的DNS査找失敗,卽使這個域名是完全可靠的。而隨後的錯誤信息揭示了原因:這個程序一次性創建了太多網絡連接,超過了每一個進程的打開文件數限製,旣而導致了在調用net.Dial像DNS査找失敗這樣的問題。
|
||||
|
||||
這個程序實在是太他媽併行了。無窮無盡地併行化併不是什麽好事情,因爲不管怎麽説,你的繫統總是會有一個些限製因素,比如CPU覈心數會限製你的計算負載,比如你的硬盤轉軸和磁頭數限製了你的本地磁盤IO操作頻率,比如你的網絡帶寬限製了你的下載速度上限,或者是你的一個web服務的服務容量上限等等。爲了解決這個問題,我們可以限製併發程序所使用的資源來使之適應自己的運行環境。對於我們的例子來説,最簡單的方法就是限製對links.Extract在同一時間最多不會有超過n次調用,這里的n是fd的limit-20,一般情況下。這個一個夜店里限製客人數目是一個道理,隻有當有客人離開時,才會允許新的客人進入店內(譯註:作者你個老流氓)。
|
||||
|
||||
@@ -109,7 +109,7 @@ Tick函數挺方便,但是隻有當程序整個生命週期都需要這個時
|
||||
|
||||
```go
|
||||
ticker := time.NewTicker(1 * time.Second)
|
||||
<-ticker.C // receive from the ticker's channel
|
||||
<-ticker.C // receive from the ticker's channel
|
||||
ticker.Stop() // cause the ticker's goroutine to terminate
|
||||
```
|
||||
|
||||
@@ -126,6 +126,7 @@ default:
|
||||
// do nothing
|
||||
}
|
||||
```
|
||||
|
||||
channel的零值是nil。也許會讓你覺得比較奇怪,nil的channel有時候也是有一些用處的。因爲對一個nil的channel發送和接收操作會永遠阻塞,在select語句中操作nil的channel永遠都不會被select到。
|
||||
|
||||
這使得我們可以用nil來激活或者禁用case,來達成處理其它輸入或輸出事件時超時和取消的邏輯。我們會在下一節中看到一個例子。
|
||||
|
||||
@@ -32,7 +32,6 @@ ioutil.ReadDir函數會返迴一個os.FileInfo類型的slice,os.FileInfo類型
|
||||
|
||||
下面的主函數,用了兩個goroutine。後台的goroutine調用walkDir來遍歷命令行給出的每一個路徑併最終關閉fileSizes這個channel。主goroutine會對其從channel中接收到的文件大小進行纍加,併輸出其和。
|
||||
|
||||
|
||||
```go
|
||||
package main
|
||||
|
||||
@@ -75,7 +74,9 @@ func printDiskUsage(nfiles, nbytes int64) {
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
這個程序會在打印其結果之前卡住很長時間。
|
||||
|
||||
```
|
||||
$ go build gopl.io/ch8/du1
|
||||
$ ./du1 $HOME /usr /bin /etc
|
||||
@@ -176,11 +177,8 @@ func dirents(dir string) []os.FileInfo {
|
||||
sema <- struct{}{} // acquire token
|
||||
defer func() { <-sema }() // release token
|
||||
// ...
|
||||
|
||||
```
|
||||
|
||||
這個版本比之前那個快了好幾倍,盡管其具體效率還是和你的運行環境,機器配置相關。
|
||||
|
||||
練習8.9: 編寫一個du工具,每隔一段時間將root目録下的目録大小計算併顯示出來。
|
||||
|
||||
|
||||
|
||||
@@ -63,7 +63,6 @@ func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
在walkDir函數的循環中我們對取消狀態進行輪詢可以帶來明顯的益處,可以避免在取消事件發生時還去創建goroutine。取消本身是有一些代價的;想要快速的響應需要對程序邏輯進行侵入式的脩改。確保在取消發生之後不要有代價太大的操作可能會需要脩改你代碼里的很多地方,但是在一些重要的地方去檢査取消事件也確實能帶來很大的好處。
|
||||
|
||||
@@ -24,7 +24,6 @@ func main() {
|
||||
|
||||
然後是broadcaster的goroutine。他的內部變量clients會記録當前建立連接的客戶端集合。其記録的內容是每一個客戶端的消息發出channel的"資格"信息。
|
||||
|
||||
|
||||
```go
|
||||
type client chan<- string // an outgoing message channel
|
||||
|
||||
@@ -90,6 +89,7 @@ func clientWriter(conn net.Conn, ch <-chan string) {
|
||||
另外,handleConn爲每一個客戶端創建了一個clientWriter的goroutine來接收向客戶端發出消息channel中發送的廣播消息,併將它們寫入到客戶端的網絡連接。客戶端的讀取方循環會在broadcaster接收到leaving通知併關閉了channel後終止。
|
||||
|
||||
下面演示的是當服務器有兩個活動的客戶端連接,併且在兩個窗口中運行的情況,使用netcat來聊天:
|
||||
|
||||
```
|
||||
$ go build gopl.io/ch8/chat
|
||||
$ go build gopl.io/ch8/netcat3
|
||||
@@ -110,7 +110,6 @@ You are 127.0.0.1:64216 127.0.0.1:64216 has arrived
|
||||
127.0.0.1:64211: Welcome. 127.0.0.1:64211: Welcome.
|
||||
^C
|
||||
127.0.0.1:64211 has left”
|
||||
|
||||
```
|
||||
|
||||
當與n個客戶端保持聊天session時,這個程序會有2n+2個併發的goroutine,然而這個程序卻併不需要顯式的鎖(§9.2)。clients這個map被限製在了一個獨立的goroutine中,broadcaster,所以它不能被併發地訪問。多個goroutine共享的變量隻有這些channel和net.Conn的實例,兩個東西都是併發安全的。我們會在下一章中更多地解決約束,併發安全以及goroutine中共享變量的含義。
|
||||
|
||||
Reference in New Issue
Block a user