pull/19/merge
Xargin 2017-05-03 10:48:04 +08:00
parent 7f9ba35f58
commit dc92697fec
3 changed files with 5 additions and 5 deletions

View File

@ -49,6 +49,6 @@ func main() {
} }
``` ```
调用counter(naturals)将导致将`chan int`类型的naturals隐式地转换为`chan<- int`channelprinter(squares)`<-chan int`channelchannelchannel`chan<- int`channel`chan int`channel 调用counternaturalsnaturals的类型将隐式地从chan int转换成chan<- intprinter(squares)`<-chan int`channelchannelchannel`chan<- int`channel`chan int`channel

View File

@ -41,7 +41,7 @@ func makeThumbnails2(filenames []string) {
这个版本运行的实在有点太快实际上由于它比最早的版本使用的时间要短得多即使当文件名的slice中只包含有一个元素。这就有点奇怪了如果程序没有并发执行的话那为什么一个并发的版本还是要快呢答案其实是makeThumbnails在它还没有完成工作之前就已经返回了。它启动了所有的goroutine每一个文件名对应一个但没有等待它们一直到执行完毕。 这个版本运行的实在有点太快实际上由于它比最早的版本使用的时间要短得多即使当文件名的slice中只包含有一个元素。这就有点奇怪了如果程序没有并发执行的话那为什么一个并发的版本还是要快呢答案其实是makeThumbnails在它还没有完成工作之前就已经返回了。它启动了所有的goroutine每一个文件名对应一个但没有等待它们一直到执行完毕。
没有什么直接的办法能够等待goroutine完成但是我们可以改变goroutine里的代码让其能够将完成情况报告给外部的goroutine知晓使用的方式是向一个共享的channel中发送事件。因为我们已经知道内部的goroutine只有len(filenames)所以外部的goroutine只需要在返回之前对这些事件计数。 没有什么直接的办法能够等待goroutine完成但是我们可以改变goroutine里的代码让其能够将完成情况报告给外部的goroutine知晓使用的方式是向一个共享的channel中发送事件。因为我们已经确切地知道有len(filenames)个内部goroutine所以外部的goroutine只需要在返回之前对这些事件计数。
```go ```go
// makeThumbnails3 makes thumbnails of the specified files in parallel. // makeThumbnails3 makes thumbnails of the specified files in parallel.

View File

@ -38,7 +38,7 @@ 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中进行的这是为了避免channel两端的main goroutine与crawler goroutine都尝试向对方发送内容却没有一端接收内容时发生死锁。当然这里我们也可以用buffered channel来解决问题这里不再赘述。
现在爬虫可以高并发地运行起来并且可以产生一大坨的URL了不过还是会有俩问题。一个问题是在运行一段时间后可能会出现在log的错误信息里的 现在爬虫可以高并发地运行起来并且可以产生一大坨的URL了不过还是会有俩问题。一个问题是在运行一段时间后可能会出现在log的错误信息里的
@ -58,7 +58,7 @@ https://golang.org/blog/
最初的错误信息是一个让人莫名的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一般小于文件描述符的上限值比如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{}来作为其元素。
@ -115,7 +115,7 @@ func main() {
现在这个并发爬虫会比5.6节中的深度优先搜索版快上20倍而且不会出什么错并且在其完成任务时也会正确地终止。 现在这个并发爬虫会比5.6节中的深度优先搜索版快上20倍而且不会出什么错并且在其完成任务时也会正确地终止。
下面的程序是避免过度并发的另一种思路。这个版本使用了原来的crawl函数但没有使用计数信号量取而代之用了20个长活的crawler goroutine这样来保证最多20个HTTP请求在并发。 下面的程序是避免过度并发的另一种思路。这个版本使用了原来的crawl函数但没有使用计数信号量取而代之用了20个常驻的crawler goroutine这样来保证最多20个HTTP请求在并发。
```go ```go
func main() { func main() {