mirror of
https://github.com/gopl-zh/gopl-zh.github.com.git
synced 2025-12-18 11:44:20 +08:00
@@ -112,7 +112,7 @@ $ killall clock1
|
||||
|
||||
killall命令是一個Unix命令行工具,可以用給定的進程名來殺掉所有名字匹配的進程。
|
||||
|
||||
第二個客戶端必鬚等待第一個客戶端完成工作,這樣服務端才能繼續向後執行;因爲我們這里的服務器程序同一時間隻能處理一個客戶端連接。我們這里對服務端程序做一點小改動,使其支持併發:在handleConn函數調用的地方增加go關鍵字,讓每一次handleConn的調用都進入一個獨立的goroutine。
|
||||
第二個客戶端必須等待第一個客戶端完成工作,這樣服務端才能繼續向後執行;因爲我們這里的服務器程序同一時間隻能處理一個客戶端連接。我們這里對服務端程序做一點小改動,使其支持併發:在handleConn函數調用的地方增加go關鍵字,讓每一次handleConn的調用都進入一個獨立的goroutine。
|
||||
|
||||
```go
|
||||
gopl.io/ch8/clock2
|
||||
|
||||
@@ -72,7 +72,7 @@ func request(hostname string) (response string) { /* ... */ }
|
||||
|
||||
關於無緩存或帶緩存channels之間的選擇,或者是帶緩存channels的容量大小的選擇,都可能影響程序的正確性。無緩存channel更強地保證了每個發送操作與相應的同步接收操作;但是對於帶緩存channel,這些操作是解耦的。同樣,卽使我們知道將要發送到一個channel的信息的數量上限,創建一個對應容量大小帶緩存channel也是不現實的,因爲這要求在執行任何接收操作之前緩存所有已經發送的值。如果未能分配足夠的緩衝將導致程序死鎖。
|
||||
|
||||
Channel的緩存也可能影響程序的性能。想象一家蛋糕店有三個廚師,一個烘焙,一個上醣衣,還有一個將每個蛋糕傳遞到它下一個廚師在生産線。在狹小的廚房空間環境,每個廚師在完成蛋糕後必鬚等待下一個廚師已經準備好接受它;這類似於在一個無緩存的channel上進行溝通。
|
||||
Channel的緩存也可能影響程序的性能。想象一家蛋糕店有三個廚師,一個烘焙,一個上糖衣,還有一個將每個蛋糕傳遞到它下一個廚師在生産線。在狹小的廚房空間環境,每個廚師在完成蛋糕後必須等待下一個廚師已經準備好接受它;這類似於在一個無緩存的channel上進行溝通。
|
||||
|
||||
如果在每個廚師之間有一個放置一個蛋糕的額外空間,那麽每個廚師就可以將一個完成的蛋糕臨時放在那里而馬上進入下一個蛋糕在製作中;這類似於將channel的緩存隊列的容量設置爲1。隻要每個廚師的平均工作效率相近,那麽其中大部分的傳輸工作將是迅速的,個體之間細小的效率差異將在交接過程中瀰補。如果廚師之間有更大的額外空間——也是就更大容量的緩存隊列——將可以在不停止生産線的前提下消除更大的效率波動,例如一個廚師可以短暫地休息,然後在加快趕上進度而不影響其其他人。
|
||||
|
||||
|
||||
@@ -176,9 +176,9 @@ func makeThumbnails6(filenames <-chan string) int64 {
|
||||
}
|
||||
```
|
||||
|
||||
註意Add和Done方法的不對策。Add是爲計數器加一,必鬚在worker goroutine開始之前調用,而不是在goroutine中;否則的話我們沒辦法確定Add是在"closer" goroutine調用Wait之前被調用。併且Add還有一個參數,但Done卻沒有任何參數;其實它和Add(-1)是等價的。我們使用defer來確保計數器卽使是在出錯的情況下依然能夠正確地被減掉。上面的程序代碼結構是當我們使用併發循環,但又不知道迭代次數時很通常而且很地道的寫法。
|
||||
註意Add和Done方法的不對策。Add是爲計數器加一,必須在worker goroutine開始之前調用,而不是在goroutine中;否則的話我們沒辦法確定Add是在"closer" goroutine調用Wait之前被調用。併且Add還有一個參數,但Done卻沒有任何參數;其實它和Add(-1)是等價的。我們使用defer來確保計數器卽使是在出錯的情況下依然能夠正確地被減掉。上面的程序代碼結構是當我們使用併發循環,但又不知道迭代次數時很通常而且很地道的寫法。
|
||||
|
||||
sizes channel攜帶了每一個文件的大小到main goroutine,在main goroutine中使用了range loop來計算總和。觀察一下我們是怎樣創建一個closer goroutine,併讓其等待worker們在關閉掉sizes channel之前退出的。兩步操作:wait和close,必鬚是基於sizes的循環的併發。考慮一下另一種方案:如果等待操作被放在了main goroutine中,在循環之前,這樣的話就永遠都不會結束了,如果在循環之後,那麽又變成了不可達的部分,因爲沒有任何東西去關閉這個channel,這個循環就永遠都不會終止。
|
||||
sizes channel攜帶了每一個文件的大小到main goroutine,在main goroutine中使用了range loop來計算總和。觀察一下我們是怎樣創建一個closer goroutine,併讓其等待worker們在關閉掉sizes channel之前退出的。兩步操作:wait和close,必須是基於sizes的循環的併發。考慮一下另一種方案:如果等待操作被放在了main goroutine中,在循環之前,這樣的話就永遠都不會結束了,如果在循環之後,那麽又變成了不可達的部分,因爲沒有任何東西去關閉這個channel,這個循環就永遠都不會終止。
|
||||
|
||||
圖8.5 表明了makethumbnails6函數中事件的序列。縱列表示goroutine。窄線段代表sleep,粗線段代表活動。斜線箭頭代表用來同步兩個goroutine的事件。時間向下流動。註意main goroutine是如何大部分的時間被喚醒執行其range循環,等待worker發送值或者closer來關閉channel的。
|
||||
|
||||
|
||||
@@ -116,7 +116,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循環。
|
||||
|
||||
現在程序會悠閒地爲我們打印更新流:
|
||||
|
||||
|
||||
Reference in New Issue
Block a user