diff --git a/eBook/14.2.md b/eBook/14.2.md index 44ca38f..f902d15 100644 --- a/eBook/14.2.md +++ b/eBook/14.2.md @@ -203,9 +203,111 @@ buf是通道可以承受的元素(这里是string)个数 在缓冲满载(缓冲被全部使用)之前,给一个带缓冲的通道发送数据是不会阻塞的,而从通道读取数据也不会阻塞,直到缓冲空了。 -缓冲容量和类型无关,所以可以(尽管可能导致危险)给一些通道设置不同的容量,只要他们拥有同样的元素类型。 +缓冲容量和类型无关,所以可以(尽管可能导致危险)给一些通道设置不同的容量,只要他们拥有同样的元素类型。内置的`cap`函数可以返回缓冲区的容量。 +如果容量大于0,通道就是异步的了:缓冲满载(发送)或变空(接收)之前通信不会阻塞,元素会按照发送的顺序被接收。如果容量是0或者未设置,通信仅在收发双方准备好的情况下才可以成功。 +同步:ch :=make(chan type, value) + +value == 0 -> synchronous, unbuffered (阻塞) + +value > 0 -> asynchronous, buffered(非阻塞)取决于value元素 + +若使用通道的缓冲,你的程序会在“请求”激增的时候表现更好:更具弹性,专业术语叫:更具有伸缩性(scalable)。要在首要位置使用无缓冲通道来设计算法,只在不确定的情况下使用缓冲。 + +练习 14.3:[channel_buffer.go](exercises/chapter_14/channel_buffer.go):给[channel_block3.go](exercises/chapter_14/channel_block3.go)的通道增加缓冲并观察输出有何不同。 + +## 14.2.6 协程中用通道输出结果 + +为了知道计算何时完成,可以通过信道回报。在例子`go sum(bigArray)`中,要这样写: +```go +ch := make(chan int) +go sum(bigArray, ch) // bigArray puts the calculated sum on ch +// .. do something else for a while +sum := <- ch // wait for, and retrieve the sum +``` +也可以使用通道来达到同步的目的,这个很有效的用法在传统计算机中成为(semaphore)。或者换个方式:通过通道发送信号告知处理已经完成(在协程中)。 + +在其他协程运行时让main程序无限阻塞的通常做法是在`main`函数的最后放置一个{}。 + +也可以使用通道让`main`程序等待协程完成,就是所谓的信号灯模式,我们会在接下来的部分讨论。 + +## 14.2.7 信号灯模式 + +下边的片段阐明:协程通过在通道`ch`中放置一个值来处理结束的信号。`main`协程等待`<-ch`直到从中获取到值。 + +我们期望从这个通道中获取返回的结果,像这样: +```go +func compute(ch chan int){ + ch <- someComputation() // when it completes, signal on the channel. +} + +func main(){ + ch := make(chan int) // allocate a channel. + go compute(ch) // stat something in a goroutines + doSomethingElseForAWhile() + result := <- ch +} +``` +这个信号也可以是其他的,不反回结果,比如下边这个协程中的lambda函数 +协程: +```go +ch := make(chan int) +go func(){ + // doSomething + ch <- 1 // Send a signal; value does not matter +} +doSomethingElseForAWhile() +<- ch // Wait for goroutine to finish; discard sent value. +``` +或者等待两个协程完成,每一个都会对切片s的一部分进行排序,片段如下: +```go +done := make(chan bool) +// doSort is a lambda function, so a closure which knows the channel done: +doSort := func(s []int){ + sort(s) + done <- true +} +i := pivot(s) +go doSort(s[:i]) +go doSort(s[i:]) +<-done +<-done +``` +下边的代码,用完整的信号灯模式对size长度的gloat64切片进行了N个`doSomething()`计算并同时完成,通道sem分配了相同的长度(切包含空接口类型的元素),待所有的计算都完成后,发送信号(通过放入值)。在循环中从通道sem不停的接收数据来等待所有的协程完成。 +```go +type Empty interface {} +var empty Empty +... +data := make([]float64, N) +res := make([]float64, N) +sem := make(chan Empty, N) +... +for i, xi := range data { + go func (i int, xi float64) { + res[i] = doSomething(i, xi) + sem <- empty + } (i, xi) +} +// wait for goroutines to finish +for i := 0; i < N; i++ { <-sem } +``` +注意闭合:`i`,`xi`都是作为参数传入闭合函数的,从外层循环中隐藏了变量`i`和`xi`。让每个协程有一份`i`和`xi`的拷贝;另外,for循环的下一次迭代会更新所有协程中`i`和`xi`的值。切片`res`没有传入闭合函数,因为协程不需要单独拷贝一份。切片`res`也在闭合函数中但并不是参数。 + +## 14.2.8 实现并行的for循环 + +在上一部分章节[14.2.7](14.2.7.md)的代码片段中:for循环的每一个迭代是并行完成的: +```go +for i, v := range data { + go func (i int, v float64) { + doSomething(i, v) + ... + } (i, v) +} +``` +在for循环中并行计算迭代可能带来很好的性能提升。不过所有的迭代都必须是独立完成的。有些语言比如Fortress或者其他并行框架以不同的结构实现了这种方式,在Go中用协程实现起来非常容易: + +## 14.2.9 用带缓冲通道实现一个信号灯 ## 链接