理解代码 - Go并发模式:Daisy Chain

时间:2014-10-01 06:49:40

标签: concurrency go

我正在研究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)
}

结论

  1. 通道从右向左流动。写作是一种很好的做法 func f(left chan<- int, right <-chan int)而不是上面的原始函数签名。

  2. &#39;连锁反应&#39;直到c <-1时才开始,当信号1被发送到最右边的信道时, 反应一路走到最左边。打印出10001。

  3. 原因是去频道阻止&#39;阅读&#39;直到收到频道接收信号。

    1. @ Rick-777展示了如何使用类似数组的结构以便于理解。 因为每个go coroutine只有大约6k大。制作10k频道并不是一个坏主意。

    2. 我清理了B点周围的一些代码,用于通道初始化。 这是源代码: http://play.golang.org/p/1kFYPypr0l

2 个答案:

答案 0 :(得分:3)

它说明你可以生成大量的goroutines。

此处,每个go f(left, right)阻止:left <- 1 + <-right阻止,因为它等待right获取值。请参阅“do golang channels maintain order” 此处创建的所有频道均为无缓冲频道。

创建了所有10000个goroutine。

B点:使用short variable declaration声明rightleft

  • 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中,区别在于作为参数传递的通道明确指定了它们的方向,即。 <-chanchan<-。这样做很好,因为编译器可以为您捕获更多错误。

具有菊花链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])
}

我希望您现在可以看到原始程序与此程序的对应关系。有一个小的区别:原始程序不会创建任何通道数组,因此只使用少一点内存。