mirror of
https://github.com/unknwon/the-way-to-go_ZH_CN.git
synced 2025-08-12 03:55:28 +08:00
Update 14.2.md
This commit is contained in:
101
eBook/14.2.md
101
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循环
|
||||
|
||||
|
||||
## 链接
|
||||
|
||||
|
Reference in New Issue
Block a user