make loop

This commit is contained in:
chai2010
2016-01-03 10:31:20 +08:00
parent 9c6e513f1d
commit 62221be1ea
7 changed files with 27 additions and 33 deletions

View File

@@ -1,6 +1,6 @@
## 8.5. 併行的循環
中,我们会探索一些用来在并行时循环迭代的常见并发模型。我们会探究全尺寸片生成一些缩略图的问题。gopl.io/ch8/thumbnail包提供了ImageFile函数来帮我们拉伸片。我们不会说明这个函数的实现,只需要gopl.io下它。
中,我們會探索一些用來在併行時循環迭代的常見併發模型。我們會探究全尺寸片生成一些縮略圖的問題。gopl.io/ch8/thumbnail包提供了ImageFile函數來幫我們拉伸片。我們不會説明這個函數的實現,隻需要gopl.io下它。
```go
gopl.io/ch8/thumbnail
@@ -11,7 +11,7 @@ package thumbnail
func ImageFile(infile string) (string, error)
```
下面的程序会循环迭代一些片文件名,并为每一张图片生成一个缩略图
下面的程序會循環迭代一些片文件名,併爲每一張圖片生成一個縮略圖
```go
gopl.io/ch8/thumbnail
@@ -25,9 +25,9 @@ func makeThumbnails(filenames []string) {
}
```
然我们处理文件的顺序无关紧要,因每一个图片的拉伸操作和其它片的理操作都是彼此立的。像这种子问题都是完全彼此立的问题被叫做易并行问题(译注embarrassingly parallel译的话更像是尴尬并行)。易并行问题是最容易被实现成并行的一类问题(废话)且是最能享受并发带来的好,能够随着并行的规模线性地展。
然我們處理文件的順序無關緊要,因每一個圖片的拉伸操作和其它片的理操作都是彼此立的。像這種子問題都是完全彼此立的問題被叫做易併行問題(譯註embarrassingly parallel譯的話更像是尷尬併行)。易併行問題是最容易被實現成併行的一類問題(廢話)且是最能享受併發帶來的好,能夠隨着併行的規模線性地展。
下面让我们并行地执行这些操作,从而将文件IO的延迟隐藏掉,用上多cpu的算能力拉伸像。我的第一个并发程序是使用了一go关键字。里我先忽略掉错误,之后再进行处理。
下面讓我們併行地執行這些操作,從而將文件IO的延遲隱藏掉,用上多cpu的算能力拉伸像。我的第一個併發程序是使用了一go關鍵字。里我先忽略掉錯誤,之後再進行處理。
```go
// NOTE: incorrect!
@@ -38,9 +38,9 @@ func makeThumbnails2(filenames []string) {
}
```
这个版本行的在有太快,实际上,由它比最早的版本使用的时间要短得多,即使当文件名的slice中包含有一元素。就有奇怪了,如果程序没有并发执行的,那为什么一个并发的版本是要快呢?答案其是makeThumbnails在它还没有完成工作之前就已经返回了。它启动了所有的goroutine没一个文件名对应一个,但有等待它一直到行完
這個版本行的在有太快,實際上,由它比最早的版本使用的時間要短得多,卽使當文件名的slice中包含有一元素。就有奇怪了,如果程序沒有併發執行的,那爲什麽一個併發的版本是要快呢?答案其是makeThumbnails在它還沒有完成工作之前就已經返迴了。它啟動了所有的goroutine沒一個文件名對應一個,但有等待它一直到行完
有什直接的法能等待goroutine完成但是我可以改goroutine里的代码让其能够将完成情况报告给外部的goroutine知,使用的方式是向一共享的channel中送事件。因为我们已经知道部的goroutine有len(filenames)所以外部的goroutine需要在返之前对这些事件计数
有什直接的法能等待goroutine完成但是我可以改goroutine里的代碼讓其能夠將完成情況報告給外部的goroutine知,使用的方式是向一共享的channel中送事件。因爲我們已經知道部的goroutine有len(filenames)所以外部的goroutine需要在返之前對這些事件計數
```go
// makeThumbnails3 makes thumbnails of the specified files in parallel.
@@ -59,7 +59,7 @@ func makeThumbnails3(filenames []string) {
}
```
意我们将f的值作为一个显式的变量传给了函,而不是在循环的闭包中明:
意我們將f的值作爲一個顯式的變量傳給了函,而不是在循環的閉包中明:
```go
@@ -71,9 +71,9 @@ for _, f := range filenames {
}
```
回忆一下之前在5.6.1中,匿名函中的循环变量快照问题。上面这个单独的变量f是被所有的匿名函值所共享,且会被连续的循迭代所更新的。新的goroutine开始执行字面函数时for循可能已更新了f并且开始了另一的迭代或者(更有可能的)已经结束了整个循环,所以当这些goroutine开始读取f的值,它所看到的值已是slice的最后一个元素了。式地添加这个参数,我们能够确保使用的f是go语句执行时的“前”那f。
迴憶一下之前在5.6.1中,匿名函中的循環變量快照問題。上面這個單獨的變量f是被所有的匿名函值所共享,且會被連續的循迭代所更新的。新的goroutine開始執行字面函數時for循可能已更新了f併且開始了另一的迭代或者(更有可能的)已經結束了整個循環,所以當這些goroutine開始讀取f的值,它所看到的值已是slice的最後一個元素了。式地添加這個參數,我們能夠確保使用的f是go語句執行時的“前”那f。
如果我想要每一worker goroutine往主goroutine中返回值时该怎么办呢?当我们调用thumbnail.ImageFile建文件失败的时候,它会返回一个错误。下一版本的makeThumbnails会返回其在做拉伸操作接收到的第一个错误
如果我想要每一worker goroutine往主goroutine中返迴值時該怎麽辦呢?當我們調用thumbnail.ImageFile建文件失敗的時候,它會返迴一個錯誤。下一版本的makeThumbnails會返迴其在做拉伸操作接收到的第一個錯誤
```go
// makeThumbnails4 makes thumbnails for the specified files in parallel.
@@ -98,11 +98,11 @@ func makeThumbnails4(filenames []string) error {
}
```
这个程序有一微秒的bug。它遇到第一非nil的error时会直接error返回到调用方,使得有一goroutine去排空errors channel。这样剩下的worker goroutine在向这个channel中送值,都会永远地阻塞下去,且永都不退出。这种情况叫做goroutine露(§8.4.4),可能会导致整程序卡住或者跑出out of memory的错误
這個程序有一微秒的bug。它遇到第一非nil的error時會直接error返迴到調用方,使得有一goroutine去排空errors channel。這樣剩下的worker goroutine在向這個channel中送值,都會永遠地阻塞下去,且永都不退出。這種情況叫做goroutine露(§8.4.4),可能會導致整程序卡住或者跑出out of memory的錯誤
简单的解决办法就是用一具有合大小的buffered channel这样这些worker goroutine向channel中发送测向时就不被阻塞。(一个可选的解决办法是建一另外的goroutinemain goroutine返第一个错误的同去排空channel)
簡單的解決辦法就是用一具有合大小的buffered channel這樣這些worker goroutine向channel中發送測向時就不被阻塞。(一個可選的解決辦法是建一另外的goroutinemain goroutine返第一個錯誤的同去排空channel)
下一版本的makeThumbnails使用了一buffered channel来返回生成的片文件的名字,附生成时的错误
下一版本的makeThumbnails使用了一buffered channel來返迴生成的片文件的名字,附生成時的錯誤
```go
// makeThumbnails5 makes thumbnails for the specified files in parallel.
@@ -136,9 +136,9 @@ func makeThumbnails5(filenames []string) (thumbfiles []string, err error) {
}
```
们最后一个版本的makeThumbnails返了新文件的大小总计数(bytes)。和前面的版本都不一的一是我们在这个版本里有把文件名放在slice里而是通过一个string的channel传过来,所以我们无法对循环的次数进行预测
們最後一個版本的makeThumbnails返了新文件的大小總計數(bytes)。和前面的版本都不一的一是我們在這個版本里有把文件名放在slice里而是通過一個string的channel傳過來,所以我們無法對循環的次數進行預測
了知道最后一个goroutine什么时候结束(最后一个结束并不一定是最后一个开始),我需要一个递增的计数器,在每一goroutine启动时加一在goroutine退出时减一。需要一特殊的计数器,这个计数器需要在多goroutine操作做到安全且提供提供在其减为零之前一直等待的一方法。这种计数类型被称为sync.WaitGroup下面的代就用到了这种方法:
了知道最後一個goroutine什麽時候結束(最後一個結束併不一定是最後一個開始),我需要一個遞增的計數器,在每一goroutine啟動時加一在goroutine退出時減一。需要一特殊的計數器,這個計數器需要在多goroutine操作做到安全且提供提供在其減爲零之前一直等待的一方法。這種計數類型被稱爲sync.WaitGroup下面的代就用到了這種方法:
```go
// makeThumbnails6 makes thumbnails for each file received from the channel.
@@ -176,14 +176,14 @@ 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的。
8.5 表明了makethumbnails6函中事件的序列。列表示goroutine。窄段代表sleep段代表活。斜線箭頭代表用同步兩個goroutine的事件。時間向下流動。註意main goroutine是如何大部分的時間被喚醒執行其range循等待worker送值或者closer來關閉channel的。
![](../images/ch8-05.png)
练习8.4: 改reverb2服器,在每一个连接中使用sync.WaitGroup来计数活跃的echo goroutine。当计数减为零时,关闭TCP接的入,像练习8.3中一样。验证一下你的改版netcat3客户端会一直等待所有的并发“喊叫”完成,使是在标准输入流已经关闭的情下。
練習8.4: 改reverb2服器,在每一個連接中使用sync.WaitGroup來計數活躍的echo goroutine。當計數減爲零時,關閉TCP接的入,像練習8.3中一樣。驗證一下你的改版netcat3客戶端會一直等待所有的併發“喊叫”完成,使是在標準輸入流已經關閉的情下。
练习8.5: 使用一已有的CPU定的序程序比如在3.3中我们写的Mandelbrot程序或者3.2中的3-D surface算程序,并将他们的主循环改为并发形式使用channel来进行通信。在多核计算机上这个程序得到了多少速度上的改?使用多少goroutine是最合的呢?
練習8.5: 使用一已有的CPU定的序程序比如在3.3中我們寫的Mandelbrot程序或者3.2中的3-D surface算程序,併將他們的主循環改爲併發形式使用channel來進行通信。在多覈計算機上這個程序得到了多少速度上的改?使用多少goroutine是最合的呢?