From 5fb068f4f0749db68b1cda0ef7235263d7711212 Mon Sep 17 00:00:00 2001 From: liracle Date: Thu, 12 Jan 2017 14:45:22 +0800 Subject: [PATCH] update 14.7.md --- eBook/14.7.md | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/eBook/14.7.md b/eBook/14.7.md index 6d60fb8..7b6fb3f 100644 --- a/eBook/14.7.md +++ b/eBook/14.7.md @@ -1,6 +1,6 @@ # 14.7 新旧模型对比:任务和worker -设想我们需要处理很多任务;一个worker处理一项任务。任务可以被定义为一个结构体(具体的细节在这里并不重要): +假设我们需要处理很多任务;一个worker处理一项任务。任务可以被定义为一个结构体(具体的细节在这里并不重要): ```go type Task struct { @@ -64,7 +64,41 @@ worker的逻辑比较简单:从pending通道拿任务,处理后将其放到d } } ``` -todo + +这里并不使用锁:从通道得到新任务的过程没有任何竞争。随着任务数量增加,worker数量也应该相应增加,同时性能并不会像第一种方式那样下降明显。在pending通道中存在一份任务的拷贝,第一个worker从pending通道中获得第一个任务并进行处理,这里并不存在竞争(对一个通道读数据和写数据的整个过程是原子的:参见[14.2.2](14.2.md))。某一个任务会在哪一个worker中被执行是不可知的,反过来也是。worker数量的增多也会增加通信的开销,这会对性能有轻微的影响。 + +从这个简单的例子中可能很难看出第二种模式的优势,但含有复杂锁应用的程序不仅在编写上显得困难,也不容易编写正确,使用第二种模式的话,就无需考虑这么复杂的东西了。 + +因此,第二种模式对比第一种模式而言,不仅性能是一个主要优势,而且还有个更大的优势:代码显得更清晰、更优雅。一个更符合go语言习惯的worker写法: + +**IDIOM: Use an in- and out-channel instead of locking** + +```go + func Worker(in, out chan *Task) { + for { + t := <-in + process(t) + out <- t + } + } +``` + +对于任何可以建模为Master-Worker范例的问题,一个类似于worker使用通道进行通信和交互、Master进行整体协调的方案都能完美解决。如果系统部署在多台机器上,各个机器上执行Worker协程,Master和Worker之间使用netchan或者RPC进行通信(参见15章)。 + +怎么选择是该使用锁还是通道? + +通道是一个较新的概念,本节我们着重强调了在go协程里通道的使用,但这并不意味着经典的锁方法就不能使用。go语言让你可以根据实际问题进行选择:创建一个优雅、简单、可读性强、在大多数场景性能表现都能很好的方案。如果你的问题适合使用锁,也不要忌讳使用它。go语言注重实用,什么方式最能解决你的问题就用什么方式,而不是强迫你使用一种编码风格。下面列出一个普遍的经验法则: + +* 使用锁的情景: + - 访问共享数据结构中的缓存信息 + - 保存应用程序上下文和状态信息数据 + +* 使用通道的情景: + - 与异步操作的结果进行交互 + - 分发任务 + - 传递数据所有权 + +当你发现你的锁使用规则变得很复杂时,可以反省使用通道会不会使问题变得简单些。 ## 链接