create 14.8.md

This commit is contained in:
liracle
2017-01-12 20:33:00 +08:00
parent 5fb068f4f0
commit 3293ac15ea
2 changed files with 67 additions and 8 deletions

View File

@@ -18,7 +18,7 @@ type Task struct {
Tasks []Task
}
```
sync.Mutex(参见9.3)是互斥锁它用来在代码中保护临界区资源同一时间只有一个协程goroutine可以进入该临界区。如果出现了同一时间多个协程都进入了该临界区则会产生竞争Pool结构就不能保证被正确更新。在传统的模式经典面向对象的语言中应用得比较多比如C++,JAVA,C#)中,工作线程代码可能这样写:
sync.Mutex([参见9.3](09.3.md)是互斥锁:它用来在代码中保护临界区资源:同一时间只有一个go协程goroutine可以进入该临界区。如果出现了同一时间多个go协程都进入了该临界区则会产生竞争Pool结构就不能保证被正确更新。在传统的模式(经典面向对象的语言中应用得比较多比如C++,JAVA,C#)worker代码可能这样写:
```go
func Worker(pool *Pool) {
@@ -34,11 +34,11 @@ func Worker(pool *Pool) {
}
```
这些worker有许多都可以并发执行他们当然可以在协程中启动。一个worker先将pool锁定从pool获取第一项任务再解锁和处理任务。加锁保证了同一时间只有协程可以进入到pool中一项任务有且只能被赋予一个work。如果不加锁,则工作协程可能会在`task:=pool.Task[0]`发生中断,导致`pool.Tasks=pool.Task[1:]`产出异常:一些工作协程获取不到任务,而一些任务可能被多个工作协程得到。加锁实现同步的方式在工作协程比较少时可以工作的很好,但是当工作协程数量很大,任务量也很多时,处理效率将会因为频繁的加锁/解锁开销而降低。当工作协程数增加到一个阈值时,程序效率会急剧下降,这就成为了瓶颈。
这些worker有许多都可以并发执行他们可以在go协程中启动。一个worker先将pool锁定从pool获取第一项任务再解锁和处理任务。加锁保证了同一时间只有一个go协程可以进入到pool中一项任务有且只能被赋予一个worer。如果不加锁,则工作协程可能会在`task:=pool.Task[0]`发生切换,导致`pool.Tasks=pool.Task[1:]`结果异常:一些worker获取不到任务,而一些任务可能被多个worker得到。加锁实现同步的方式在工作协程比较少时可以工作的很好,但是当工作协程数量很大,任务量也很多时,处理效率将会因为频繁的加锁/解锁开销而降低。当工作协程数增加到一个阈值时,程序效率会急剧下降,这就成为了瓶颈。
新模式:使用通道
使用通道进行同步:使用一个通道接受需要处理的任务,一个通道接受处理完成的任务(及其结果)。工作处理者在协程中启动其数量N应该根据任务数量进行调整。
使用通道进行同步:使用一个通道接受需要处理的任务,一个通道接受处理完成的任务(及其结果)。worker在协程中启动其数量N应该根据任务数量进行调整。
主线程扮演着Master节点角色可能写成如下形式
@@ -65,9 +65,9 @@ worker的逻辑比较简单从pending通道拿任务处理后将其放到d
}
```
这里并不使用锁从通道得到新任务的过程没有任何竞争。随着任务数量增加worker数量也应该相应增加同时性能并不会像第一种方式那样下降明显。在pending通道中存在一份任务的拷贝第一个worker从pending通道中获得第一个任务并进行处理这里并不存在竞争对一个通道读数据和写数据的整个过程是原子的参见[14.2.2](14.2.md))。某一个任务会在哪一个worker中被执行是不可知的反过来也是。worker数量的增多也会增加通信的开销这会对性能有轻微的影响。
这里并不使用锁从通道得到新任务的过程没有任何竞争。随着任务数量增加worker数量也应该相应增加同时性能并不会像第一种方式那样下降明显。在pending通道中存在一份任务的拷贝第一个worker从pending通道中获得第一个任务并进行处理这里并不存在竞争对一个通道读数据和写数据的整个过程是原子的:参见[14.2.2](14.2.md))。某一个任务会在哪一个worker中被执行是不可知的反过来也是。worker数量的增多也会增加通信的开销这会对性能有轻微的影响。
从这个简单的例子中可能很难看出第二种模式的优势,但含有复杂锁用的程序不仅在编写上显得困难,也不容易编写正确,使用第二种模式的话,就无需考虑这么复杂的东西了。
从这个简单的例子中可能很难看出第二种模式的优势,但含有复杂锁用的程序不仅在编写上显得困难,也不容易编写正确,使用第二种模式的话,就无需考虑这么复杂的东西了。
因此第二种模式对比第一种模式而言不仅性能是一个主要优势而且还有个更大的优势代码显得更清晰、更优雅。一个更符合go语言习惯的worker写法
@@ -102,6 +102,6 @@ worker的逻辑比较简单从pending通道拿任务处理后将其放到d
## 链接
[目录](directory.md)
上一节:[协程和恢复recover](14.6.md)
下一节:[惰性生成器实现](14.8.md)
- [目录](directory.md)
- 上一节:[协程和恢复recover](14.6.md)
- 下一节:[惰性生成器实现](14.8.md)