mirror of
https://github.com/unknwon/the-way-to-go_ZH_CN.git
synced 2025-08-12 03:34:15 +08:00
Update 14.2.md
This commit is contained in:
104
eBook/14.2.md
104
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 用带缓冲通道实现一个信号灯
|
||||
|
||||
## 链接
|
||||
|
||||
|
Reference in New Issue
Block a user