我正在研究Go并发模式。
我不确定的一种模式是:菊花链https://talks.golang.org/2012/concurrency.slide#39
我很难理解代码的控制流程。
有人可以向我解释一下吗?
package main
import (
"fmt"
)
func f(left, right chan int) {
left <- 1 + <-right
}
func main() {
const n = 10000
leftmost := make(chan int)
right := leftmost //point B: what does these do ?
left := leftmost
for i := 0; i < n; i++ {
right = make(chan int)
go f(left, right)
left = right //point A
}
go func(c chan int) { c <- 1 }(right)
fmt.Println(<-leftmost)
}
结论:
通道从右向左流动。写作是一种很好的做法
func f(left chan<- int, right <-chan int)
而不是上面的原始函数签名。
&#39;连锁反应&#39;直到c <-1时才开始,当信号1被发送到最右边的信道时, 反应一路走到最左边。打印出10001。
原因是去频道阻止&#39;阅读&#39;直到收到频道接收信号。
@ Rick-777展示了如何使用类似数组的结构以便于理解。 因为每个go coroutine只有大约6k大。制作10k频道并不是一个坏主意。
我清理了B点周围的一些代码,用于通道初始化。 这是源代码: http://play.golang.org/p/1kFYPypr0l
答案 0 :(得分:3)
它说明你可以生成大量的goroutines。
此处,每个go f(left, right)
阻止:left <- 1 + <-right
阻止,因为它等待right
获取值。请参阅“do golang channels maintain order”
此处创建的所有频道均为无缓冲频道。
创建了所有10000个goroutine。
B点:使用short variable declaration声明right
和left
。
right
被初始化为最左边,但没关系,因为它会被重新分配到for
循环中的新频道(right = make(chan int)
)。
声明权利的另一种方式是:
var right chan int
left
初始化为leftmost
,即创建的第一个频道。
A点:但是一旦该频道开始等待(left <- 1 + <-right
),for
循环就会设置为right
,并创建一个新的right
:这就是菊花链正在构建
left <- (new) right (now left) <- (new) right (now left) <- ...
然后,会向创建的最后一个right
频道发送一个值:{c <- 1 }(right)
等待创建的第一个leftmost
频道接收其值(增加10000次)。
由于接收器始终阻塞,直到有数据要接收,main()
函数本身不会在leftmost
最终收到其值之前退出。
如果main()
过早退出,则菊花链将没有时间完成。
答案 1 :(得分:3)
VonC已经给出了直接答案。以下是一些进一步的评论。
稍微整理的版本在the playground中,区别在于作为参数传递的通道明确指定了它们的方向,即。 <-chan
和chan<-
。这样做很好,因为编译器可以为您捕获更多错误。
具有菊花链n
goroutines的替代和等效程序可以使用通道数组来编写。这使用较少的代码行分配相同的通道总数。见playground:
package main
import (
"fmt"
)
func f(left chan<- int, right <-chan int) {
left <- 1 + <-right
}
func main() {
const n = 10000
// first we construct an array of n+1 channels each being a 'chan int'
var channels [n+1]chan int
for i := range channels {
channels[i] = make(chan int)
}
// now we wire n goroutines in a chain
for i := 0; i < n; i++ {
go f(channels[i], channels[i+1])
}
// insert a value into the right-hand end
go func(c chan<- int) { c <- 1 }(channels[n])
// pick up the value emerging from the left-hand end
fmt.Println(<-channels[0])
}
我希望您现在可以看到原始程序与此程序的对应关系。有一个小的区别:原始程序不会创建任何通道数组,因此只使用少一点内存。