有时你可能会发现自己考虑将一个或多个done通道合并到一个done通道中,该通道在任何组件通道关闭时关闭。编写一个执行这种耦合度较高的select语句是可行的,尽管很冗长;但是有时你无法知道运行状态下done通道的数量。在这种情况下,或者你如果喜欢单线操作,你可以使用or通道模式将这些通道组合在一起(如果你对done通道看着有点懵,可以先看看上一节)。
这种模式使用递归和goroutine创建一个复合done通道。 我们来看一下:
```
var or func(channels ...<-chan interface{}) <-chan interface{}
or = func(channels ...<-chan interface{}) <-chan interface{} { //1
switch len(channels) {
case 0: //2
return nil
case 1: //3
return channels[0]
}
orDone := make(chan interface{})
go func() { //4
defer close(orDone)
switch len(channels) {
case 2: //5
select {
case <-channels[0]:
case <-channels[1]:
}
default: //6
select {
case <-channels[0]:
case <-channels[1]:
case <-channels[2]:
case <-or(append(channels[3:], orDone)...): //6
}
}
}()
return orDone
}
```
1. 这里我们建立了名为or的函数,接收数量可变的通道并返回单个通道。
2. 由于这是个递归函数,我们必须设置终止条件。第一个条件是,如果传入的切片是空的,我们简单的返回一个nil通道。这与不传递通道的想法一致:我们不希望复合通道做任何事。
3. 第二个递归终止条件是,如果切片只含有一个元素,我们就返回给元素。
4. 这是该函数最重要的部分,也是递归产生的地方。我们建立一个goroutine,以便可以不受阻塞地等待我们通道上的消息。
5. 由于我们这里是递归的,每次递归调用将至少有两个通道。作为保持goroutine数量受到限制的优化方法,们在这里为仅使用两个通道的时设置了一个特殊情况。
6. 在这里,我们递归地在第三个索引之后,从我们切片中的所有通道中创建一个or通道,然后从中选择。递归操作会逐层累计直到取到第一个通道元素。我们在其中传递了orDone通道,这样当该树状结构顶层的goroutines退出时,结构底层的goroutines也会退出。
这是一种奇妙的做法,你可以将任意数量的通道组合到单个通道中,只要任何作为组件的通道关闭或被写入,整个通道就会关闭。让我们来看看该如何进行实际操作。下面这个例子将经过一段时间后关闭通道,然后使用or函数将这些通道合并到一个关闭的通道中:
```
sig := func(after time.Duration) <-chan interface{} { //1
c := make(chan interface{})
go func() {
defer close(c)
time.Sleep(after)
}()
return c
}
start := time.Now() //2
<-or(sig(2*time.Hour), sig(5*time.Minute), sig(1*time.Second), sig(1*time.Hour), sig(1*time.Minute))
fmt.Printf("done after %v", time.Since(start)) //3
```
1. 此功能只是创建了一个通道,当后续时间中指定的时间结束时将关闭该通道。
2. 在这里,我们设置追踪自or函数的通道开始阻塞的起始时间。
3. 在这里我们打印阻塞发生的时间。
这会输出:
```
done after 1.000216772s
```
请注意,尽管在我们的调用中放置了多个通道需要多个时间才能关闭,但我们在一秒钟后关闭的通道会导致由该d调用创建的整个通道关闭。 这是因为它位于树或函数构建的树中,它将始终第一个关闭,因此依赖于其关闭的通道也将关闭。
我们以额外创建 f(x)=x/2 个goroutine以"简洁的"实现该目的,其中x是goroutine的数量。请记住Go的一个优点是能够快速创建,调度和运行goroutines,并且 该语言积极鼓励使用goroutines来正确建模问题。无需在前期太担心在这里创建的分支太多。如果在编译时你不知道自己正在使用多少个done通道,那么恐怕就没有其他更好的方法来合并done通道了。
这种模式适用于系统中模块的交叉点。在这些交叉点,有多种条件通过你的调用堆栈取消goroutines树。 使用or函数,你可以简单地将它们组合在一起并将其传递给堆栈。 我们将在“context包”中看到另一种更具描述性的做法。
我们也将看到这种模式的变体在第五章“重复请求”中形成更复杂的模式。
* * * * *
学识浅薄,错误在所难免。我是长风,欢迎来Golang中国的群(211938256)就本书提出修改意见。
- 前序
- 谁适合读这本书
- 章节导读
- 在线资源
- 第一章 并发编程介绍
- 摩尔定律,可伸缩网络和我们所处的困境
- 为什么并发编程如此困难
- 数据竞争
- 原子性
- 内存访问同步
- 死锁,活锁和锁的饥饿问题
- 死锁
- 活锁
- 饥饿
- 并发安全性
- 优雅的面对复杂性
- 第二章 代码建模:序列化交互处理
- 并发与并行
- 什么是CSP
- CSP在Go中的衍生物
- Go的并发哲学
- 第三章 Go的并发构建模块
- Goroutines
- sync包
- WaitGroup
- Mutex和RWMutex
- Cond
- Once
- Pool
- Channels
- select语句
- GOMAXPROCS
- 结论
- 第四章 Go的并发编程范式
- 访问范围约束
- fo-select循环
- 防止Goroutine泄漏
- or-channel
- 错误处理
- 管道
- 构建管道的最佳实践
- 便利的生成器
- 扇入扇出
- or-done-channel
- tee-channel
- bridge-channel
- 队列
- context包
- 小结
- 第五章 可伸缩并发设计
- 错误传递
- 超时和取消
- 心跳
- 请求并发复制处理
- 速率限制
- Goroutines异常行为修复
- 本章小结
- 第六章 Goroutines和Go运行时
- 任务调度