有时你可能会发现自己考虑将一个或多个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
}
- 这里我们建立了名为or的函数,接收数量可变的通道并返回单个通道。
- 由于这是个递归函数,我们必须设置终止条件。第一个条件是,如果传入的切片是空的,我们简单的返回一个nil通道。这与不传递通道的想法一致:我们不希望复合通道做任何事。
- 第二个递归终止条件是,如果切片只含有一个元素,我们就返回给元素。
- 这是该函数最重要的部分,也是递归产生的地方。我们建立一个goroutine,以便可以不受阻塞地等待我们通道上的消息。
- 由于我们这里是递归的,每次递归调用将至少有两个通道。作为保持goroutine数量受到限制的优化方法,们在这里为仅使用两个通道的时设置了一个特殊情况。
- 在这里,我们递归地在第三个索引之后,从我们切片中的所有通道中创建一个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
- 此功能只是创建了一个通道,当后续时间中指定的时间结束时将关闭该通道。
- 在这里,我们设置追踪自or函数的通道开始阻塞的起始时间。
- 在这里我们打印阻塞发生的时间。
这会输出:
done after 1.000216772s
请注意,尽管在我们的调用中放置了多个通道需要多个时间才能关闭,但我们在一秒钟后关闭的通道会导致由该d调用创建的整个通道关闭。 这是因为它位于树或函数构建的树中,它将始终第一个关闭,因此依赖于其关闭的通道也将关闭。
我们以额外创建 f(x)=x/2 个goroutine以”简洁的”实现该目的,其中x是goroutine的数量。请记住Go的一个优点是能够快速创建,调度和运行goroutines,并且 该语言积极鼓励使用goroutines来正确建模问题。无需在前期太担心在这里创建的分支太多。如果在编译时你不知道自己正在使用多少个done通道,那么恐怕就没有其他更好的方法来合并done通道了。
这种模式适用于系统中模块的交叉2021-01-02 15:56:03 星期六点。在这些交叉点,有多种条件通过你的调用堆栈取消goroutines树。 使用or函数,你可以简单地将它们组合在一起并将其传递给堆栈。 我们将在“context包”中看到另一种更具描述性的做法。
我们也将看到这种模式的变体在第五章“重复请求”中形成更复杂的模式。