This commit is contained in:
Xargin
2016-10-05 13:42:01 +08:00
parent 5fd1e1d48f
commit f2df739842
6 changed files with 19 additions and 19 deletions

View File

@@ -26,7 +26,7 @@ func makeThumbnails(filenames []string) {
}
```
显然我们处理文件的顺序无关紧要,因为每一个图片的拉伸操作和其它图片的处理操作都是彼此独立的。像这种子问题都是完全彼此独立的问题被叫做易并行问题(译注embarrassingly parallel直译的话更像是尴尬并行)。易并行问题是最容易被实现成并行的一类问题(废话),并且最能够享受并发带来的好处,能够随着并行的规模线性地扩展。
显然我们处理文件的顺序无关紧要,因为每一个图片的拉伸操作和其它图片的处理操作都是彼此独立的。像这种子问题都是完全彼此独立的问题被叫做易并行问题(译注embarrassingly parallel直译的话更像是尴尬并行)。易并行问题是最容易被实现成并行的一类问题(废话),并且最能够享受并发带来的好处,能够随着并行的规模线性地扩展。
下面让我们并行地执行这些操作从而将文件IO的延迟隐藏掉并用上多核cpu的计算能力来拉伸图像。我们的第一个并发程序只是使用了一个go关键字。这里我们先忽略掉错误之后再进行处理。
@@ -39,7 +39,7 @@ func makeThumbnails2(filenames []string) {
}
```
这个版本运行的实在有点太快实际上由于它比最早的版本使用的时间要短得多即使当文件名的slice中只包含有一个元素。这就有点奇怪了如果程序没有并发执行的话那为什么一个并发的版本还是要快呢答案其实是makeThumbnails在它还没有完成工作之前就已经返回了。它启动了所有的goroutine一个文件名对应一个,但没有等待它们一直到执行完毕。
这个版本运行的实在有点太快实际上由于它比最早的版本使用的时间要短得多即使当文件名的slice中只包含有一个元素。这就有点奇怪了如果程序没有并发执行的话那为什么一个并发的版本还是要快呢答案其实是makeThumbnails在它还没有完成工作之前就已经返回了。它启动了所有的goroutine一个文件名对应一个,但没有等待它们一直到执行完毕。
没有什么直接的办法能够等待goroutine完成但是我们可以改变goroutine里的代码让其能够将完成情况报告给外部的goroutine知晓使用的方式是向一个共享的channel中发送事件。因为我们已经知道内部的goroutine只有len(filenames)所以外部的goroutine只需要在返回之前对这些事件计数。
@@ -137,7 +137,7 @@ func makeThumbnails5(filenames []string) (thumbfiles []string, err error) {
我们最后一个版本的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,7 +176,7 @@ func makeThumbnails6(filenames <-chan string) int64 {
注意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 goroutine们结束之后再关闭sizes channel的。两步操作wait和close必须是基于sizes的循环的并发。考虑一下另一种方案如果等待操作被放在了main goroutine中在循环之前这样的话就永远都不会结束了如果在循环之后那么又变成了不可达的部分因为没有任何东西去关闭这个channel这个循环就永远都不会终止。
图8.5 表明了makethumbnails6函数中事件的序列。纵列表示goroutine。窄线段代表sleep粗线段代表活动。斜线箭头代表用来同步两个goroutine的事件。时间向下流动。注意main goroutine是如何大部分的时间被唤醒执行其range循环等待worker发送值或者closer来关闭channel的。