Skip to content

Commit

Permalink
Update 14.2.md
Browse files Browse the repository at this point in the history
  • Loading branch information
glight2000 committed Dec 26, 2015
1 parent 5095fe0 commit 7f82cbd
Showing 1 changed file with 97 additions and 4 deletions.
101 changes: 97 additions & 4 deletions eBook/14.2.md
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,9 @@ sum := <- ch // wait for, and retrieve the sum

在其他协程运行时让main程序无限阻塞的通常做法是在`main`函数的最后放置一个{}。

也可以使用通道让`main`程序等待协程完成,就是所谓的信号灯模式,我们会在接下来的部分讨论。
也可以使用通道让`main`程序等待协程完成,就是所谓的信号量模式,我们会在接下来的部分讨论。

## 14.2.7 信号灯模式
## 14.2.7 信号量模式

下边的片段阐明:协程通过在通道`ch`中放置一个值来处理结束的信号。`main`协程等待`<-ch`直到从中获取到值。

Expand Down Expand Up @@ -274,7 +274,7 @@ go doSort(s[i:])
<-done
<-done
```
下边的代码,用完整的信号灯模式对size长度的gloat64切片进行了N个`doSomething()`计算并同时完成,通道sem分配了相同的长度(切包含空接口类型的元素),待所有的计算都完成后,发送信号(通过放入值)。在循环中从通道sem不停的接收数据来等待所有的协程完成。
下边的代码,用完整的信号量模式对size长度的gloat64切片进行了N个`doSomething()`计算并同时完成,通道sem分配了相同的长度(切包含空接口类型的元素),待所有的计算都完成后,发送信号(通过放入值)。在循环中从通道sem不停的接收数据来等待所有的协程完成。
```go
type Empty interface {}
var empty Empty
Expand Down Expand Up @@ -307,7 +307,100 @@ for i, v := range data {
```
在for循环中并行计算迭代可能带来很好的性能提升。不过所有的迭代都必须是独立完成的。有些语言比如Fortress或者其他并行框架以不同的结构实现了这种方式,在Go中用协程实现起来非常容易:

## 14.2.9 用带缓冲通道实现一个信号灯
## 14.2.9 用带缓冲通道实现一个信号量

信号量是实现互斥锁(排外锁)常见的同步机制,限制对资源的访问,解决读写问题,比如没有实现信号量的`sync`的Go包,使用带缓冲的通道可以轻松实现:

* 带缓冲通道的容量和我们要同步的资源容量相同
* 通道的长度(当前存放的元素个数)当前资源被使用的数量相同
* 容量减去通道的长度就是未处理的资源个数(标准信号量的整数值)

不用管通道中存放的是什么,只关注长度;因此我们创建了一个有长度变量为0(字节)的通道:
```go
type Empty interface {}
type semaphore chan Empty
```
将可用资源的数量N来初始化信号量`semaphore``sem = make(semaphore, N)`

然后直接对信号量进行操作:
```go
// acquire n resources
func (s semaphore) P(n int) {
e := new(Empty)
for i := 0; i < n; i++ {
s <- e
}
}

// release n resouces
func (s semaphore) V(n int) {
for i:= 0; i < n; i++{
<- s
}
}
```
可以用来实现一个互斥的例子:
```go
/* mutexes */
func (s semaphore) Lock() {
s.P(1)
}

func (s semaphore) Unlock(){
s.V(1)
}

/* signal-wait */
func (s semaphore) Wait(n int) {
s.P(n)
}

func (s semaphore) Signal() {
s.V(1)
}
```
练习 14.5:[gosum.go](exercises/chapter_14/gosum.go):用这种习惯用法写一个程序,开启一个协程来计算2个整数的合并等待计算结果并打印出来。

练习 14.6:[producer_consumer.go](exercises/chapter_14/producer_consumer.go):用这种习惯用法写一个程序,有两个协程,第一个提供数字0,10,20,...90并将他们放入通道,第二个协程从通道中读取并打印。`main()`等待两个协程完成后再结束。

习惯用法:通道工厂模式

编程中常见另外一种模式如下:不将通道作为参数传递给协程,而用函数来生成一个通道并返回(工厂角色);函数内有个lambda函数被协程调用。

[channel_block2.go](examples/chapter_14/channel_block2.go)加入这种模式便有了示例 14.5-[channel_idiom.go](examples/chapter_14/channel_idiom.go)
```go
package main

import (
"fmt"
"time"
)

func main() {
stream := pump()
go suck(stream)
time.Sleep(1e9)
}

func pump() chan int {
ch := make(chan int)
go func() {
for i := 0; ; i++ {
ch <- i
}
}()
return ch
}

func suck(ch chan int) {
for {
fmt.Println(<-ch)
}
}
```

## 14.2.10 给通道使用For循环


## 链接

Expand Down

0 comments on commit 7f82cbd

Please sign in to comment.