|
|
@@ -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 用带缓冲通道实现一个信号灯
|
|
|
|
|
|
## 链接
|
|
|
|