update tw

This commit is contained in:
chai2010
2015-12-18 10:53:03 +08:00
parent 510c741a6f
commit c66a96ee52
106 changed files with 864 additions and 864 deletions

View File

@@ -1,17 +1,17 @@
## 8.1. Goroutines
在Go語言中每一個併髮的執行單元叫作一個goroutine。設想這有一個程序有兩個函數一個函數做一些計算另一個輸齣一些結果假設兩個函數沒有相互之間的調用關繫。一個綫性的程序會先調用其中的一個函數然後再調用來一個但如果是在有兩個甚至更多個goroutine的程序中對兩個函數的調用就可以在一時間。我們馬上就會看到這樣的一個程序。
在Go語言中每一個並發的執行單元叫作一個goroutine。設想這有一個程序有兩個函數一個函數做一些計算另一個輸齣一些結果假設兩個函數沒有相互之間的調用關繫。一個綫性的程序會先調用其中的一個函數然後再調用來一個但如果是在有兩個甚至更多個goroutine的程序中對兩個函數的調用就可以在一時間。我們馬上就會看到這樣的一個程序。
如果你使用過操作繫統或者其它語言提供的綫程,那你可以簡單地把goroutine類比作一個綫程這樣你就可以寫齣一些正確的程序了。goroutine和綫程的本質區會在9.8節中講。
如果你使用過操作繫統或者其它語言提供的綫程,那你可以簡單地把goroutine類比作一個綫程這樣你就可以寫齣一些正確的程序了。goroutine和綫程的本質區會在9.8節中講。
當一個程序動時其主函數卽在一個單獨的goroutine中運行我們叫它main goroutine。新的goroutine會用go語句來創建。在語法上go語句是一個普通的函數或方法調用前加上關鍵字go。go語句會使其語句中的函數在一個新創建的goroutine中運行。而go語句本身會迅速地完成。
當一個程序動時其主函數卽在一個單獨的goroutine中運行我們叫它main goroutine。新的goroutine會用go語句來創建。在語法上go語句是一個普通的函數或方法調用前加上關鍵字go。go語句會使其語句中的函數在一個新創建的goroutine中運行。而go語句本身會迅速地完成。
```go
f() // call f(); wait for it to return
go f() // create a new goroutine that calls f(); don't wait
```
在下的例子中main goroutine會計算第45個菲波那契數。由於計算函數使用了效率非常低的遞歸所以會運行相當可觀的一段時間在這期間我們想要讓用戶看到一個可見的標識來明程序依然在正常運行,所以顯示一個動畫的小圖標:
在下的例子中main goroutine會計算第45個菲波那契數。由於計算函數使用了效率非常低的遞歸所以會運行相當可觀的一段時間在這期間我們想要讓用戶看到一個可見的標識來明程序依然在正常運行,所以顯示一個動畫的小圖標:
```go
gopl.io/ch8/spinner
@@ -40,10 +40,10 @@ func fib(x int) int {
```
動畫顯示了幾秒之後fib(45)的調用成功地返迴,且打印結果:
動畫顯示了幾秒之後fib(45)的調用成功地返迴,且打印結果:
Fibonacci(45) = 1134903170
然後主函數返迴。當主函數返迴時所有的goroutine都會直接打斷程序退齣。除了從主函數退齣或者直接退齣程序之外沒有其它的編程方法能夠讓一個goroutine來打斷另一個的執行但是我們之後可以看到可以通過goroutine之間的通信來讓一個goroutine請求請求其它的goroutine讓其自己結束執行。
然後主函數返迴。當主函數返迴時所有的goroutine都會直接打斷程序退齣。除了從主函數退齣或者直接退齣程序之外沒有其它的編程方法能夠讓一個goroutine來打斷另一個的執行但是我們之後可以看到可以通過goroutine之間的通信來讓一個goroutine請求請求其它的goroutine讓其自己結束執行。
註意這的兩個獨立的單元是如何進行組spinning和菲波那契的計算。每一個都是寫在獨立的函數中但是每一個函數都會併髮地執行。
註意這的兩個獨立的單元是如何進行組spinning和菲波那契的計算。每一個都是寫在獨立的函數中但是每一個函數都會並發地執行。

View File

@@ -1,6 +1,6 @@
## 8.2. 示例: 併髮的Clock服務
## 8.2. 示例: 並發的Clock服務
網絡編程是併髮大顯身手的一個領域,由於服務器是最典型的需要時處理很多連接的程序這些連接一般來自遠彼此獨立的客戶端。在本小節中我們會講解go語言的net包這個包提供編寫一個網絡客戶端或者服務器程序的基本組件無論兩者間通信是使用TCPUDP或者Unix domain sockets。在第一章中我們已經使用過的net/http包的方法也算是net包的一部分。
網絡編程是並發大顯身手的一個領域,由於服務器是最典型的需要時處理很多連接的程序這些連接一般來自遠彼此獨立的客戶端。在本小節中我們會講解go語言的net包這個包提供編寫一個網絡客戶端或者服務器程序的基本組件無論兩者間通信是使用TCPUDP或者Unix domain sockets。在第一章中我們已經使用過的net/http包的方法也算是net包的一部分。
我們的第一個例子是一個順序執行的時鍾服務器,它會每隔一秒鍾將當前時間寫到客戶端:
```go
@@ -44,13 +44,13 @@ func handleConn(c net.Conn) {
```
Listen函數創建了一個net.Listener的對象這個對象會監聽一個網絡端口上到來的連接在這個例子我們用的是TCP的localhost:8000端口。listener對象的Accept方法會直接阻塞直到一個新的連接被創建然後會返迴一個net.Conn對象來示這個連接。
Listen函數創建了一個net.Listener的對象這個對象會監聽一個網絡端口上到來的連接在這個例子我們用的是TCP的localhost:8000端口。listener對象的Accept方法會直接阻塞直到一個新的連接被創建然後會返迴一個net.Conn對象來示這個連接。
handleConn函數會處理一個完整的客戶端連接。在一個for死循環中將當前的時候用time.Now()函數得到然後寫到客戶端。由於net.Conn實現了io.Writer接口我們可以直接其寫入內容。這個死循環會一直執行直到寫入失敗。最可能的原因是客戶端主動斷開連接。這種情況下handleConn函數會用defer調用關閉服務器側的連接然後返迴到主函數繼續等待下一個連接請求。
handleConn函數會處理一個完整的客戶端連接。在一個for死循環中將當前的時候用time.Now()函數得到然後寫到客戶端。由於net.Conn實現了io.Writer接口我們可以直接其寫入內容。這個死循環會一直執行直到寫入失敗。最可能的原因是客戶端主動斷開連接。這種情況下handleConn函數會用defer調用關閉服務器側的連接然後返迴到主函數繼續等待下一個連接請求。
time.Time.Format方法提供了一種格式化日期和時間信息的方式。它的參數是一個格式化模闆標識如何來格式化時間而這個格式化模闆限定Mon Jan 2 03:04:05PM 2006 UTC-0700。有8個部分(週幾,月份,一個月的第幾天,等等)。可以以任意的形式來組閤前麫這個模闆;齣現在模闆中的部分會作參考來對時間格式進行輸齣。在上的例子中我們隻用到了小時、分鍾和秒。time包定義了很多標準時間格式比如time.RFC1123。在進行格式化的逆操作time.Parse時也會用到樣的策略。(譯註這是go語言和其它語言相比比較奇葩的一個地方。。你需要記住格式化字符串是1月2日下午3點4分5秒零六年UTC-0700而不像其它語言那樣Y-m-d H:i:s一樣當然了這可以用1234567的方式來記憶倒是也不麻煩)
time.Time.Format方法提供了一種格式化日期和時間信息的方式。它的參數是一個格式化模闆標識如何來格式化時間而這個格式化模闆限定Mon Jan 2 03:04:05PM 2006 UTC-0700。有8個部分(週幾,月份,一個月的第幾天,等等)。可以以任意的形式來組合前面這個模闆;齣現在模闆中的部分會作參考來對時間格式進行輸齣。在上的例子中我們隻用到了小時、分鍾和秒。time包定義了很多標準時間格式比如time.RFC1123。在進行格式化的逆操作time.Parse時也會用到樣的策略。(譯註這是go語言和其它語言相比比較奇葩的一個地方。。你需要記住格式化字符串是1月2日下午3點4分5秒零六年UTC-0700而不像其它語言那樣Y-m-d H:i:s一樣當然了這可以用1234567的方式來記憶倒是也不麻煩)
了連接例子的服務器我們需要一個客戶端程序比如netcat這個工具(nc命令),這個工具可以用來執行網絡連接操作。
了連接例子的服務器我們需要一個客戶端程序比如netcat這個工具(nc命令),這個工具可以用來執行網絡連接操作。
```
$ go build gopl.io/ch8/clock1
@@ -63,7 +63,7 @@ $ nc localhost 8000
^C
```
客戶端將服務器來的時間顯示了齣來我們用Control+C來中斷客戶端的執行在Unix繫統上你會看到^C這樣的響應。如果你的繫統沒有裝nc這個工具你可以用telnet來實現樣的效果,或者也可以用我們下的這個用go寫的簡單的telnet程序用net.Dial就可以簡單地創建一個TCP連接
客戶端將服務器來的時間顯示了齣來我們用Control+C來中斷客戶端的執行在Unix繫統上你會看到^C這樣的響應。如果你的繫統沒有裝nc這個工具你可以用telnet來實現樣的效果,或者也可以用我們下的這個用go寫的簡單的telnet程序用net.Dial就可以簡單地創建一個TCP連接
```go
gopl.io/ch8/netcat1
@@ -92,7 +92,7 @@ func mustCopy(dst io.Writer, src io.Reader) {
}
}
```
這個程序會從連接中讀取數據,將讀到的內容寫到標準輸齣中直到遇到end of file的條件或者生錯誤。mustCopy這個函數我們在本節的幾個例子中都會用到。讓我們時運行兩個客戶端來進行一個測試,這可以開兩個終端窗口,下左邊的是其中的一個的輸齣,右邊的是另一個的輸齣:
這個程序會從連接中讀取數據,將讀到的內容寫到標準輸齣中直到遇到end of file的條件或者生錯誤。mustCopy這個函數我們在本節的幾個例子中都會用到。讓我們時運行兩個客戶端來進行一個測試,這可以開兩個終端窗口,下左邊的是其中的一個的輸齣,右邊的是另一個的輸齣:
```
$ go build gopl.io/ch8/netcat1
@@ -110,7 +110,7 @@ $ killall clock1
killall命令是一個Unix命令行工具可以用給定的進程名來殺掉所有名字匹配的進程。
第二個客戶端必鬚等待第一個客戶端完成工作,這樣服務端纔能繼續後執行;因我們這的服務器程序一時間隻能處理一個客戶端連接。我們這對服務端程序做一點小改動,使其支持併髮在handleConn函數調用的地方增加go關鍵字讓每一次handleConn的調用都進入一個獨立的goroutine。
第二個客戶端必鬚等待第一個客戶端完成工作,這樣服務端纔能繼續後執行;因我們這的服務器程序一時間隻能處理一個客戶端連接。我們這對服務端程序做一點小改動,使其支持並發在handleConn函數調用的地方增加go關鍵字讓每一次handleConn的調用都進入一個獨立的goroutine。
```go
gopl.io/ch8/clock2
@@ -124,7 +124,7 @@ for {
}
```
現在多個客戶端可以時接收到時間了:
現在多個客戶端可以時接收到時間了:
```
$ go build gopl.io/ch8/clock2
@@ -144,7 +144,7 @@ $ ./netcat1
$ killall clock2
```
練習8.1: 脩改clock2來支持傳入參數作端口號然後寫一個clockwall的程序這個程序可以時與多個clock服務器通信從多服務器中讀取時間且在一個格中一次顯示所有服務傳迴的結果,類似於你在某些辦公室看到的時鍾牆。如果你有地理學上分佈式的服務器可以用的話,讓這些服務器跑在不的機器上;或者在衕一颱機器上跑多個不的實例,這些實例監聽不的端口,假裝自己在不的時區。像下這樣:
練習8.1: 脩改clock2來支持傳入參數作端口號然後寫一個clockwall的程序這個程序可以時與多個clock服務器通信從多服務器中讀取時間且在一個格中一次顯示所有服務傳迴的結果,類似於你在某些辦公室看到的時鍾牆。如果你有地理學上分佈式的服務器可以用的話,讓這些服務器跑在不的機器上;或者在同一檯機器上跑多個不的實例,這些實例監聽不的端口,假裝自己在不的時區。像下這樣:
```
$ TZ=US/Eastern ./clock2 -port 8010 &
@@ -153,4 +153,4 @@ $ TZ=Europe/London ./clock2 -port 8030 &
$ clockwall NewYork=localhost:8010 London=localhost:8020 Tokyo=localhost:8030
```
練習8.2: 實現一個併髮FTP服務器。服務器應該解析客戶端來的一些命令比如cd命令來切換目ls來列齣目內文件get和send來傳輸文件close來關閉連接。你可以用標準的ftp命令來作客戶端,或者也可以自己實現一個。
練習8.2: 實現一個並發FTP服務器。服務器應該解析客戶端來的一些命令比如cd命令來切換目ls來列齣目內文件get和send來傳輸文件close來關閉連接。你可以用標準的ftp命令來作客戶端,或者也可以自己實現一個。

View File

@@ -1,3 +1,3 @@
## 8.3. 示例: 併髮的Echo服務
## 8.3. 示例: 並發的Echo服務
TODO

View File

@@ -1,3 +1,3 @@
## 8.5. 行的循環
## 8.5. 行的循環
TODO

View File

@@ -1,6 +1,6 @@
## 8.6. 示例: 併髮的Web爬蟲
## 8.6. 示例: 並發的Web爬蟲
在5.6節中,我們做了一簡單的web爬蟲用bfs(廣度優先)算法來抓取整網站。在本節中,我們會讓這箇這箇爬蟲行化,這樣每一彼此獨立的抓取命令可以行進行IO最大化利用網絡資源。crawl函數和gopl.io/ch5/findlinks3中的是一樣的。
在5.6節中,我們做了一簡單的web爬蟲用bfs(廣度優先)算法來抓取整網站。在本節中,我們會讓這個這個爬蟲行化,這樣每一彼此獨立的抓取命令可以行進行IO最大化利用網絡資源。crawl函數和gopl.io/ch5/findlinks3中的是一樣的。
```go
gopl.io/ch8/crawl1
@@ -14,7 +14,7 @@ func crawl(url string) []string {
}
```
主函數和5.6節中的breadthFirst(深度優先)類似。像之前一樣,一worklist是一記録了需要處理的元素的隊列,每一元素都是一需要抓取的URL列不過這一次我們用channel代替slice來做這隊列。每一對crawl的調用都會在他們自己的goroutine中進行且會把他們抓到的鏈接送迴worklist。
主函數和5.6節中的breadthFirst(深度優先)類似。像之前一樣,一worklist是一記録了需要處理的元素的隊列,每一元素都是一需要抓取的URL列不過這一次我們用channel代替slice來做這隊列。每一對crawl的調用都會在他們自己的goroutine中進行且會把他們抓到的鏈接送迴worklist。
```go
func main() {
@@ -38,9 +38,9 @@ func main() {
}
```
註意這的crawl所在的goroutine會將link作為一箇顯式的數傳入,來避免“循環變量快照”的問題(在5.6.1中有講解)。另外註意這將命令行數傳入worklist也是在一另外的goroutine中進行的這是了避免在main goroutine和crawler goroutine中衕時嚮另一goroutine通過channel送內容時生死鎖(因另一邊的接收操作還沒有準備好)。然,這我們也可以用buffered channel來解決問題不再贅述。
註意這的crawl所在的goroutine會將link作爲一個顯式的數傳入,來避免“循環變量快照”的問題(在5.6.1中有講解)。另外註意這將命令行數傳入worklist也是在一另外的goroutine中進行的這是了避免在main goroutine和crawler goroutine中同時向另一goroutine通過channel送內容時生死鎖(因另一邊的接收操作還沒有準備好)。然,這我們也可以用buffered channel來解決問題不再贅述。
現在爬蟲可以高併髮地運行起來,且可以產生一大坨的URL了不過還是會有倆問題。一問題是在運行一段時間後可能會齣現在log的錯誤信息的:
現在爬蟲可以高並發地運行起來,且可以產生一大坨的URL了不過還是會有倆問題。一問題是在運行一段時間後可能會齣現在log的錯誤信息的:
```
@@ -56,13 +56,13 @@ https://golang.org/blog/
too many open files
...
```
最初的錯誤信息是一讓人莫名的DNS査找失敗卽使這域名是完全可靠的。而隨後的錯誤信息揭示了原因:這程序一次性創建了太多網絡連接,超過了每一進程的打開文件數限製旣而導緻了在調用net.Dial像DNS査找失敗這樣的問題。
最初的錯誤信息是一讓人莫名的DNS査找失敗卽使這域名是完全可靠的。而隨後的錯誤信息揭示了原因:這程序一次性創建了太多網絡連接,超過了每一進程的打開文件數限製旣而導緻了在調用net.Dial像DNS査找失敗這樣的問題。
程序實在是太他媽行了。無窮無盡地併行化不是什好事情,因不管怎麼説,你的統總是會有一些限製因素比如CPU覈心數會限製你的計算負載比如你的硬盤轉軸和磁頭數限製了你的本地磁盤IO操作頻率比如你的網絡帶寬限製了你的下載速度上限或者是你的一web服務的服務容量上限等等。了解決這問題,我們可以限製併髮程序所使用的資源來使之適應自己的運行環境。對於我們的例子來最簡單的方法就是限製對links.Extract在一時間最多不會有超過n次調用的n是fd的limit-20一般情況下。這箇一箇夜店限製客人數目是一道理,隻有有客人離開時,纔會允許新的客人進入店內(譯註:作者你老流氓)。
程序實在是太他媽行了。無窮無儘地並行化不是什好事情,因不管怎麽說,你的統總是會有一些限製因素比如CPU覈心數會限製你的計算負載比如你的硬盤轉軸和磁頭數限製了你的本地磁盤IO操作頻率比如你的網絡帶寬限製了你的下載速度上限或者是你的一web服務的服務容量上限等等。了解決這問題,我們可以限製並發程序所使用的資源來使之適應自己的運行環境。對於我們的例子來最簡單的方法就是限製對links.Extract在一時間最多不會有超過n次調用的n是fd的limit-20一般情況下。這個一個夜店限製客人數目是一道理,隻有有客人離開時,纔會允許新的客人進入店內(譯註:作者你老流氓)。
我們可以用一有容量限製的buffered channel來控製併髮,這類似於操作係統裏的計數信號量概唸。從概唸上講channel的n空槽代錶n箇可以處理內容的token(通行証)從channel接收一值會釋放其中的一token且生成一新的空槽位。這樣保証了在沒有接收介入時最多有n箇髮送操作。(這可能我們拿channel填充的槽來做token更直觀一些不過還是這樣吧~)。由於channel的元素類型不重要,我們用一零值的struct{}來作其元素。
我們可以用一有容量限製的buffered channel來控製並發,這類似於操作繫統裡的計數信號量概唸。從概唸上講channel的n空槽代表n個可以處理內容的token(通行証)從channel接收一值會釋放其中的一token且生成一新的空槽位。這樣保証了在沒有接收介入時最多有n個發送操作。(這可能我們拿channel填充的槽來做token更直觀一些不過還是這樣吧~)。由於channel的元素類型不重要,我們用一零值的struct{}來作其元素。
讓我們重寫crawl函數將對links.Extract的調用操作用取、釋放token的操作包裹起來來確保一時間對其隻有20調用。信號量數量和其能操作的IO資源數量應保持接近。
讓我們重寫crawl函數將對links.Extract的調用操作用取、釋放token的操作包裹起來來確保一時間對其隻有20調用。信號量數量和其能操作的IO資源數量應保持接近。
```go
gopl.io/ch8/crawl2
@@ -82,7 +82,7 @@ func crawl(url string) []string {
}
```
第二個問題是這個程序永遠都不會終止,卽使它已經爬到了所有初始鏈接衍生齣的鏈接。(然,除非你慎重地選擇了適的初始化URL或者已經實現了練習8.6中的深度限製,你應該還沒有意識到這個問題)。爲了使這個程序能夠終止我們需要在worklist爲空或者沒有crawl的goroutine在運行時退齣主循環。
第二個問題是這個程序永遠都不會終止,卽使它已經爬到了所有初始鏈接衍生齣的鏈接。(然,除非你慎重地選擇了適的初始化URL或者已經實現了練習8.6中的深度限製,你應該還沒有意識到這個問題)。爲了使這個程序能夠終止我們需要在worklist爲空或者沒有crawl的goroutine在運行時退齣主循環。
```go
@@ -114,11 +114,11 @@ func main() {
```
版本中計算器n對worklist的送操作數量進行了限製。每一次我們現有元素需要被送到worklist時我們都會對n進行++操作,在worklist中送初始的命令行數之前,我們也進行過一次++操作。這的操作++是在每動一crawler的goroutine之前。主循環會在n減0時終止這時候明沒活可乾了。
版本中計算器n對worklist的送操作數量進行了限製。每一次我們現有元素需要被送到worklist時我們都會對n進行++操作,在worklist中送初始的命令行數之前,我們也進行過一次++操作。這的操作++是在每動一crawler的goroutine之前。主循環會在n減0時終止這時候明沒活可乾了。
現在這箇併髮爬蟲會比5.6節中的深度優先蒐索版快上20倍而且不會齣什錯,且在其完成任務時也會正確地終止。
現在這個並發爬蟲會比5.6節中的深度優先蒐索版快上20倍而且不會齣什錯,且在其完成任務時也會正確地終止。
的程序是避免過度併髮的另一種思路。這版本使用了原來的crawl函數但沒有使用計數信號量取而代之用了20長活的crawler goroutine這樣來保証最多20HTTP請求在併髮
的程序是避免過度並發的另一種思路。這版本使用了原來的crawl函數但沒有使用計數信號量取而代之用了20長活的crawler goroutine這樣來保証最多20HTTP請求在並發
```go
func main() {
@@ -152,15 +152,15 @@ func main() {
}
```
所有的爬蟲goroutine現在都是被衕一箇channel-unseenLinks餵飽的了。主goroutine負責拆分它從worklist拿到的元素然後把沒有抓過的經由unseenLinks channel送給一爬蟲的goroutine。
所有的爬蟲goroutine現在都是被同一個channel-unseenLinks餵飽的了。主goroutine負責拆分它從worklist拿到的元素然後把沒有抓過的經由unseenLinks channel送給一爬蟲的goroutine。
seen這map被限定在main goroutine中也就是説這箇map隻能在main goroutine中進行訪問。類似於其它的信息隱藏方式這樣的約束可以讓我們從一定程度上保証程序的正確性。例如內部變量不能夠在函數外部被訪問到變量(§2.3.4)在沒有被轉義的情況下是無法在函數外部訪問的;一對象的封裝字段無法被該對象的方法以外的方法訪問到。在所有的情況下,信息隱藏都可以幫助我們約束我們的程序,使其不生意料之外的情況。
seen這map被限定在main goroutine中也就是說這個map隻能在main goroutine中進行訪問。類似於其它的信息隱藏方式這樣的約束可以讓我們從一定程度上保証程序的正確性。例如內部變量不能夠在函數外部被訪問到變量(§2.3.4)在沒有被轉義的情況下是無法在函數外部訪問的;一對象的封裝字段無法被該對象的方法以外的方法訪問到。在所有的情況下,信息隱藏都可以幫助我們約束我們的程序,使其不生意料之外的情況。
crawl函數爬到的鏈接在一專有的goroutine中被送到worklist中來避免死鎖。了節省空間,這例子的終止問題我們先不進行詳細闡述了。
crawl函數爬到的鏈接在一專有的goroutine中被送到worklist中來避免死鎖。了節省空間,這例子的終止問題我們先不進行詳細闡述了。
練習8.6: 為併髮爬蟲增加深度限製。也就是如果用戶設置了depth=3隻有從首頁跳轉三次以內能夠跳到的頁纔能被抓取到。
練習8.6: 爲並發爬蟲增加深度限製。也就是如果用戶設置了depth=3隻有從首頁跳轉三次以內能夠跳到的頁纔能被抓取到。
練習8.7: 完成一箇併髮程序來創建一箇線上網站的本地鏡像,把該站點的所有可達的頁都抓取到本地硬盤。了省事,我們這可以隻取齣現在該域下的所有頁(比如golang.org結尾譯註外鏈的應該就不算了。)然了,齣現在頁麪裏的鏈接你也需要進行一些處理,使其能夠在你的鏡像站點上進行跳轉,而不是指原始的鏈接。
練習8.7: 完成一個並發程序來創建一個綫上網站的本地鏡像,把該站點的所有可達的頁都抓取到本地硬盤。了省事,我們這可以隻取齣現在該域下的所有頁(比如golang.org結尾譯註外鏈的應該就不算了。)然了,齣現在頁面裡的鏈接你也需要進行一些處理,使其能夠在你的鏡像站點上進行跳轉,而不是指原始的鏈接。
譯註:

View File

@@ -1,6 +1,6 @@
## 8.8. 示例: 併髮的字典遍
## 8.8. 示例: 並發的字典遍
在本小節中,我們會創建一個程序來生成指定目的硬盤使用情況報告這個程序和Unix裡的du工具比較相似。大多數工作用下這個walkDir函數來完成這個函數使用dirents函數來枚舉一個目下的所有入口。
在本小節中,我們會創建一個程序來生成指定目的硬盤使用情況報告這個程序和Unix裡的du工具比較相似。大多數工作用下這個walkDir函數來完成這個函數使用dirents函數來枚舉一個目下的所有入口。
```go
gopl.io/ch8/du1
@@ -28,9 +28,9 @@ func dirents(dir string) []os.FileInfo {
}
```
ioutil.ReadDir函數會返迴一個os.FileInfo類型的sliceos.FileInfo類型也是os.Stat這個函數的返迴值。對每一個子目而言walkDir會遞歸地調用其自身且會對每一個文件也遞歸調用。walkDir函數會fileSizes這個channel發送一條消息。這條消息包含了文件的字節大小。
ioutil.ReadDir函數會返迴一個os.FileInfo類型的sliceos.FileInfo類型也是os.Stat這個函數的返迴值。對每一個子目而言walkDir會遞歸地調用其自身且會對每一個文件也遞歸調用。walkDir函數會fileSizes這個channel發送一條消息。這條消息包含了文件的字節大小。
的主函數用了兩個goroutine。後檯的goroutine調用walkDir來遍命令行給齣的每一個路徑最終關閉fileSizes這個channel。主goroutine會對其從channel中接收到的文件大小進行纍加輸齣其和。
的主函數用了兩個goroutine。後檯的goroutine調用walkDir來遍命令行給齣的每一個路徑最終關閉fileSizes這個channel。主goroutine會對其從channel中接收到的文件大小進行纍加輸齣其和。
```go
@@ -84,7 +84,7 @@ $ ./du1 $HOME /usr /bin /etc
如果在運行的時候能夠讓我們知道處理進度的話想必更好。但是如果簡單地把printDiskUsage函數調用移動到循環裡會導緻其打印齣成百上韆的輸齣。
這個du的變種會間歇打印內容不過隻有在調用時提供了-v的flag纔會顯示程序進度信息。在roots目上循環的後檯goroutine在這裡保持不變。主goroutine現在使用了計時器來每500ms生成事件然後用select語句來等待文件大小的消息來更新總大小數據或者一個計時器的事件來打印前的總大小數據。如果-v的flag在運行時沒有傳入的話tick這個channel會保持爲nil這樣在select裡的case也就相於被禁用了。
這個du的變種會間歇打印內容不過隻有在調用時提供了-v的flag纔會顯示程序進度信息。在roots目上循環的後檯goroutine在這裡保持不變。主goroutine現在使用了計時器來每500ms生成事件然後用select語句來等待文件大小的消息來更新總大小數據或者一個計時器的事件來打印前的總大小數據。如果-v的flag在運行時沒有傳入的話tick這個channel會保持爲nil這樣在select裡的case也就相於被禁用了。
```go
gopl.io/ch8/du2
@@ -115,7 +115,7 @@ loop:
printDiskUsage(nfiles, nbytes) // final totals
}
```
由於我們的程序不再使用range循環第一個select的case必顯式地判斷fileSizes的channel是不是已經被關閉了這裡可以用到channel接收的二值形式。如果channel已經被關閉了的話程序會直接退齣循環。這裡的break語句用到了標break這樣可以時終結select和for兩個循環如果沒有用標就break的話隻會退齣內層的select循環而外層的for循環會使之進入下一輪select循環。
由於我們的程序不再使用range循環第一個select的case必顯式地判斷fileSizes的channel是不是已經被關閉了這裡可以用到channel接收的二值形式。如果channel已經被關閉了的話程序會直接退齣循環。這裡的break語句用到了標break這樣可以時終結select和for兩個循環如果沒有用標就break的話隻會退齣內層的select循環而外層的for循環會使之進入下一輪select循環。
現在程序會悠閒地爲我們打印更新流:
@@ -130,7 +130,7 @@ $ ./du2 -v $HOME /usr /bin /etc
213201 files 62.7 GB
```
然而這個程序還是會花上很長時間纔會結束。無法對walkDir做行化處理沒什麽的原因,無非是因爲磁盤繫統行限製。下這個第三個版本的du會對每一個walkDir的調用創建一個新的goroutine。它使用sync.WaitGroup (§8.5)來對仍舊活躍的walkDir調用進行計數另一個goroutine會在計數器減爲零的時候將fileSizes這個channel關閉。
然而這個程序還是會花上很長時間纔會結束。無法對walkDir做行化處理沒什麽的原因,無非是因爲磁盤繫統行限製。下這個第三個版本的du會對每一個walkDir的調用創建一個新的goroutine。它使用sync.WaitGroup (§8.5)來對仍舊活躍的walkDir調用進行計數另一個goroutine會在計數器減爲零的時候將fileSizes這個channel關閉。
```go
gopl.io/ch8/du3
@@ -164,7 +164,7 @@ func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) {
}
```
由於這個程序在高峯期會創建成百上韆的goroutine我們需要脩改dirents函數用計數信號量來阻止他時打開太多的文件就像我們在8.7節中的發爬蟲一樣:
由於這個程序在高峯期會創建成百上韆的goroutine我們需要脩改dirents函數用計數信號量來阻止他時打開太多的文件就像我們在8.7節中的發爬蟲一樣:
```go
@@ -181,6 +181,6 @@ func dirents(dir string) []os.FileInfo {
這個版本比之前那個快了好幾倍,儘管其具體效率還是和你的運行環境,機器配置相關。
練習8.9: 編寫一個du工具每隔一段時間將root目下的目大小計算顯示齣來。
練習8.9: 編寫一個du工具每隔一段時間將root目下的目大小計算顯示齣來。

View File

@@ -1,14 +1,14 @@
## 8.9. 併髮的退齣
## 8.9. 並發的退齣
有時候我們需要通知goroutine停止它正在乾的事情比如一正在執行計算的web服務然而它的客戶端已經斷開了和服務端的連接。
有時候我們需要通知goroutine停止它正在乾的事情比如一正在執行計算的web服務然而它的客戶端已經斷開了和服務端的連接。
Go語言沒有提供在一goroutine中終止另一goroutine的方法由於這樣會導緻goroutine之間的共享變量落在未定義的狀態上。在8.7節中的rocket launch程序中我們往名字叫abort的channel裡發送了一簡單的值在countdown的goroutine中會把這值理解爲自己的退齣信號。但是如果我們想要退齣兩或者任意多goroutine怎辦呢?
Go語言沒有提供在一goroutine中終止另一goroutine的方法由於這樣會導緻goroutine之間的共享變量落在未定義的狀態上。在8.7節中的rocket launch程序中我們往名字叫abort的channel裡發送了一簡單的值在countdown的goroutine中會把這值理解爲自己的退齣信號。但是如果我們想要退齣兩或者任意多goroutine怎辦呢?
一種可能的手段是abort的channel裡發送和goroutine數目一樣多的事件來退齣它們。如果這些goroutine中已經有一些自己退齣了會導緻我們的channel裡的事件數比goroutine還多這樣導緻我們的發送直接被阻塞。另一方如果這些goroutine又生成了其它的goroutine我們的channel裡的數目又太少了所以有些goroutine可能會無法接收到退齣消息。一般情況下我們是很難知道在某一時刻具體有多少goroutine在運行着的。另外噹一箇goroutine從abort channel中接收到一值的時候,他會消費掉這這樣其它的goroutine就沒法看到這條信息。爲了能夠達到我們退齣goroutine的目的我們需要更靠譜的策略來通過一channel把消息廣播齣去這樣goroutine們能夠看到這條事件消息且在事件完成之後,可以知道這件事已經發生過了。
一種可能的手段是abort的channel裡發送和goroutine數目一樣多的事件來退齣它們。如果這些goroutine中已經有一些自己退齣了會導緻我們的channel裡的事件數比goroutine還多這樣導緻我們的發送直接被阻塞。另一方如果這些goroutine又生成了其它的goroutine我們的channel裡的數目又太少了所以有些goroutine可能會無法接收到退齣消息。一般情況下我們是很難知道在某一時刻具體有多少goroutine在運行着的。另外當一個goroutine從abort channel中接收到一值的時候,他會消費掉這這樣其它的goroutine就沒法看到這條信息。爲了能夠達到我們退齣goroutine的目的我們需要更靠譜的策略來通過一channel把消息廣播齣去這樣goroutine們能夠看到這條事件消息且在事件完成之後,可以知道這件事已經發生過了。
迴憶一下我們關閉了一channel且被消費掉了所有已發送的值操作channel之後的代碼可以立卽被執行且會產生零值。我們可以將這機製擴展一下,來作爲我們的廣播機製:不要channel發送值而是用關閉一channel來進行廣播。
迴憶一下我們關閉了一channel且被消費掉了所有已發送的值操作channel之後的代碼可以立卽被執行且會產生零值。我們可以將這機製擴展一下,來作爲我們的廣播機製:不要channel發送值而是用關閉一channel來進行廣播。
隻要一些小脩改我們就可以把退齣邏輯加入到前一節的du程序。首先我們創建一退齣的channelchannel不會其中發送任何值,但其所在的閉包內要寫明程序需要退齣。我們時還定義了一工具函數cancelled函數在被調用的時候會輪詢退齣狀態。
隻要一些小脩改我們就可以把退齣邏輯加入到前一節的du程序。首先我們創建一退齣的channelchannel不會其中發送任何值,但其所在的閉包內要寫明程序需要退齣。我們時還定義了一工具函數cancelled函數在被調用的時候會輪詢退齣狀態。
```go
gopl.io/ch8/du4
@@ -24,7 +24,7 @@ func cancelled() bool {
}
```
我們創建一從標準輸入流中讀取內容的goroutine這是一比較典型的連接到終端的程序。每有輸入被讀到(比如用戶按了迴車鍵),這goroutine就會把取消消息通過關閉done的channel廣播齣去。
我們創建一從標準輸入流中讀取內容的goroutine這是一比較典型的連接到終端的程序。每有輸入被讀到(比如用戶按了迴車鍵),這goroutine就會把取消消息通過關閉done的channel廣播齣去。
```go
// Cancel traversal when input is detected.
@@ -34,7 +34,7 @@ go func() {
}()
```
現在我們需要使我們的goroutine來對取消進行響應。在main goroutine中我們添加了select的第三case語句嘗試從done channel中接收內容。如果這case被滿足的話在select到的時候卽會返迴但在結束之前我們需要把fileSizes channel中的內容“排”空在channel被關閉之前捨棄掉所有值。這樣可以保証對walkDir的調用不要被fileSizes發送信息阻塞住可以正確地完成。
現在我們需要使我們的goroutine來對取消進行響應。在main goroutine中我們添加了select的第三case語句嘗試從done channel中接收內容。如果這case被滿足的話在select到的時候卽會返迴但在結束之前我們需要把fileSizes channel中的內容“排”空在channel被關閉之前捨棄掉所有值。這樣可以保証對walkDir的調用不要被fileSizes發送信息阻塞住可以正確地完成。
```go
for {
@@ -51,7 +51,7 @@ for {
}
```
walkDir這goroutine一啟動就會輪詢取消狀態如果取消狀態被設置的話會直接返迴且不做額外的事情。這樣我們將所有在取消事件之後創建的goroutine改變爲無操作。
walkDir這goroutine一啟動就會輪詢取消狀態如果取消狀態被設置的話會直接返迴且不做額外的事情。這樣我們將所有在取消事件之後創建的goroutine改變爲無操作。
```go
func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) {
@@ -68,7 +68,7 @@ func walkDir(dir string, n *sync.WaitGroup, fileSizes chan<- int64) {
在walkDir函數的循環中我們對取消狀態進行輪詢可以帶來明顯的益處可以避免在取消事件發生時還去創建goroutine。取消本身是有一些代價的想要快速的響應需要對程序邏輯進行侵入式的脩改。確保在取消發生之後不要有代價太大的操作可能會需要脩改你代碼裡的很多地方但是在一些重要的地方去檢査取消事件也確實能帶來很大的好處。
對這程序的一簡單的性能分析可以揭示瓶頸在dirents函數中取一信號量。下的select可以讓這種操作可以被取消且可以將取消時的延遲從幾百毫秒降低到幾十毫秒。
對這程序的一簡單的性能分析可以揭示瓶頸在dirents函數中取一信號量。下的select可以讓這種操作可以被取消且可以將取消時的延遲從幾百毫秒降低到幾十毫秒。
```go
func dirents(dir string) []os.FileInfo {
@@ -82,10 +82,10 @@ func dirents(dir string) []os.FileInfo {
}
```
現在取消發生時所有後檯的goroutine都會迅速停止且主函數會返迴。然,主函數返迴時,一程序會退齣,而我們又無法在主函數退齣的時候確認其已經釋放了所有的資源(譯註:因爲程序都退齣了,你的代碼都沒法執行了)。這裡有一方便的竅門我們可以一用:取代掉直接從主函數返迴,我們調用一panic然後runtime會把每一goroutine的棧dump下來。如果main goroutine是唯一一剩下的goroutine的話他會清理掉自己的一切資源。但是如果還有其它的goroutine沒有退齣他們可能沒辦法被正確地取消掉也有可能被取消但是取消操作會很花時間所以這裡的一調研還是很有必要的。我們用panic來取到足夠的信息來驗証我們上的判斷,看看最終到底是什樣的情況。
現在取消發生時所有後檯的goroutine都會迅速停止且主函數會返迴。然,主函數返迴時,一程序會退齣,而我們又無法在主函數退齣的時候確認其已經釋放了所有的資源(譯註:因爲程序都退齣了,你的代碼都沒法執行了)。這裡有一方便的竅門我們可以一用:取代掉直接從主函數返迴,我們調用一panic然後runtime會把每一goroutine的棧dump下來。如果main goroutine是唯一一剩下的goroutine的話他會清理掉自己的一切資源。但是如果還有其它的goroutine沒有退齣他們可能沒辦法被正確地取消掉也有可能被取消但是取消操作會很花時間所以這裡的一調研還是很有必要的。我們用panic來取到足夠的信息來驗証我們上的判斷,看看最終到底是什樣的情況。
練習8.10: HTTP請求可能會因http.Request結構體中Cancel channel的關閉而取消。脩改8.6節中的web crawler來支持取消http請求。
提示: http.Get沒有提供方便地定製一請求的方法。你可以用http.NewRequest來取而代之設置它的Cancel字段然後用http.DefaultClient.Do(req)來進行這http請求。
提示: http.Get沒有提供方便地定製一請求的方法。你可以用http.NewRequest來取而代之設置它的Cancel字段然後用http.DefaultClient.Do(req)來進行這http請求。
練習8.11:緊接着8.4.4中的mirroredQuery流程實現一箇併發請求url的fetch的變種。第一請求返迴時,直接取消其它的請求。
練習8.11:緊接着8.4.4中的mirroredQuery流程實現一個並發請求url的fetch的變種。第一請求返迴時,直接取消其它的請求。

View File

@@ -1,7 +1,7 @@
# 第八章 Goroutines和Channels
併髮程序指的是時做好幾件事情的程序,隨着硬件的展,併髮程序顯得越來越重要。Web服務器會一次處理成韆上萬的請求。平闆電腦和手機app在渲染用戶動畫的時,還會後執行各種計算任務和網絡請求。卽使是傳統的批處理問題--讀取數據,計算,寫輸齣--現在也會用併髮來隱藏掉I/O的操作延遲充分利用現代計算機設備的多覈管計算機的性能每年都在增長,但不是綫性。
並發程序指的是時做好幾件事情的程序,隨着硬件的展,並發程序顯得越來越重要。Web服務器會一次處理成韆上萬的請求。平闆電腦和手機app在渲染用戶動畫的時,還會後執行各種計算任務和網絡請求。卽使是傳統的批處理問題--讀取數據,計算,寫輸齣--現在也會用並發來隱藏掉I/O的操作延遲充分利用現代計算機設備的多覈管計算機的性能每年都在增長,但不是綫性。
Go語言中的併髮程序可以用兩種手段來實現。這一章會講解goroutine和channel其支持“順序進程通信”(communicating sequential processes)或被簡稱CSP。CSP是一個現代的併髮編程模型,在這種編程模型中值會在不的運行實例(goroutine)中傳遞,管大多數情況下被限製在單一實例中。第9章會覆蓋到更傳統的併髮模型:多綫程共享內存,如果你在其它的主流語言中寫過併髮程序的話可能會更熟悉一些。第9章時會講一些本章不會深入的併髮程序帶來的重要風險和陷阱。
Go語言中的並發程序可以用兩種手段來實現。這一章會講解goroutine和channel其支持“順序進程通信”(communicating sequential processes)或被簡稱CSP。CSP是一個現代的並發編程模型,在這種編程模型中值會在不的運行實例(goroutine)中傳遞,管大多數情況下被限製在單一實例中。第9章會覆蓋到更傳統的並發模型:多綫程共享內存,如果你在其它的主流語言中寫過並發程序的話可能會更熟悉一些。第9章時會講一些本章不會深入的並發程序帶來的重要風險和陷阱。
管Go對併髮的支持是多強力特性之一,但大多數情況下跟蹤併髮程序還是很睏難,且在綫性程序中我們的直覺往往還會讓我們誤入歧途。如果這是你第一次接觸併髮,那我推薦你稍微多花一些時間來思考這兩個章節中的樣例。
管Go對並發的支持是多強力特性之一,但大多數情況下跟蹤並發程序還是很睏難,且在綫性程序中我們的直覺往往還會讓我們誤入歧途。如果這是你第一次接觸並發,那我推薦你稍微多花一些時間來思考這兩個章節中的樣例。