diff --git a/eBook/14.2.md b/eBook/14.2.md index f902d153d..d70af95eb 100644 --- a/eBook/14.2.md +++ b/eBook/14.2.md @@ -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`直到从中获取到值。 @@ -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 @@ -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循环 + ## 链接