Ver código fonte

Update 14.2.md

glight2000 10 anos atrás
pai
commit
7f82cbd0cd
1 arquivos alterados com 97 adições e 4 exclusões
  1. 97 4
      eBook/14.2.md

+ 97 - 4
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循环
+
 
 ## 链接