This commit is contained in:
chai2010
2016-01-02 21:17:21 +08:00
parent 8772a9c000
commit ba03c527c0
17 changed files with 63 additions and 29 deletions

View File

@@ -41,9 +41,11 @@ func fib(x int) int {
```
動畵顯示了幾秒之後fib(45)的調用成功地返迴,併且打印結果:
```
Fibonacci(45) = 1134903170
```
然後主函數返迴。當主函數返迴時所有的goroutine都會直接打斷程序退出。除了從主函數退出或者直接退出程序之外沒有其它的編程方法能夠讓一個goroutine來打斷另一個的執行但是我們之後可以看到可以通過goroutine之間的通信來讓一個goroutine請求請求其它的goroutine併讓其自己結束執行。
註意這里的兩個獨立的單元是如何進行組合的spinning和菲波那契的計算。每一個都是寫在獨立的函數中但是每一個函數都會併發地執行。

View File

@@ -3,6 +3,7 @@
網絡編程是併發大顯身手的一個領域由於服務器是最典型的需要同時處理很多連接的程序這些連接一般來自遠彼此獨立的客戶端。在本小節中我們會講解go語言的net包這個包提供編寫一個網絡客戶端或者服務器程序的基本組件無論兩者間通信是使用TCPUDP或者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 {
}
```
現在多個客戶端可以同時接收到時間了:
```

View File

@@ -8,6 +8,7 @@ func handleConn(c net.Conn) {
c.Close()
}
```
一個更有意思的echo服務應該模擬一個實際的echo的“迴響”併且一開始要用大寫HELLO來表示“聲音很大”之後經過一小段延遲返迴一個有所緩和的Hello然後一個全小寫字母的hello表示聲音漸漸變小直至消失像下面這個版本的handleConn(譯註:笑看作者腦洞大開)
```go

View File

@@ -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一般情況下。這個一個夜店里限製客人數目是一個道理隻有當有客人離開時才會允許新的客人進入店內(譯註:作者你個老流氓)。

View File

@@ -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來達成處理其它輸入或輸出事件時超時和取消的邏輯。我們會在下一節中看到一個例子。

View File

@@ -32,7 +32,6 @@ ioutil.ReadDir函數會返迴一個os.FileInfo類型的sliceos.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目録下的目録大小計算併顯示出來。

View File

@@ -63,7 +63,6 @@ func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) {
// ...
}
}
```
在walkDir函數的循環中我們對取消狀態進行輪詢可以帶來明顯的益處可以避免在取消事件發生時還去創建goroutine。取消本身是有一些代價的想要快速的響應需要對程序邏輯進行侵入式的脩改。確保在取消發生之後不要有代價太大的操作可能會需要脩改你代碼里的很多地方但是在一些重要的地方去檢査取消事件也確實能帶來很大的好處。

View File

@@ -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中共享變量的含義。