理解代码-并发模式:菊花链

I was studying Go concurrency pattern.

One pattern I am not sure is: Daisy Chain https://talks.golang.org/2012/concurrency.slide#39

It's very hard for me to understand the control flow of the code.

Can someone explain to me ?

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)
}

Conclusion:

  1. the flow of channel going from right to left. It is good practice to write func f(left chan<- int, right <-chan int) rather than original function signature as above.

  2. 'chain reaction' does not start until c <- 1, when signal 1 is sent to right most channel, reaction goes all the way to left most end. Print out 10001.

The reason is go channel block 'read' until received channel receive signal.

  1. @Rick-777 shows how to use array like structure for easy understanding. Since each go coroutine is just around 6k big. It's not a bad idea to make 10k channel.

  2. I clean up some code around Point B, for channel initialization. Here is the source code: http://play.golang.org/p/1kFYPypr0l

VonC has already given a direct answer. Here are some further remarks.

A slightly tidied-up version is in the playground, the difference being that the channels passed as parameters have their direction specified explicitly, ie. <-chan and chan<-. It's good practice to do this because the compiler can catch more mistakes for you.

An alternative and equivalent program that has a daisy-chain of n goroutines can be written using an array of channels instead. This allocates the same total number of channels using fewer lines of code. See 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])
}

I hope you can see now how the original program is equivalent to this program. There is one minor difference: the original program does not create any channel array, so uses just a little less memory.

It illustrates you can generate a large number of goroutines.

Here, each go f(left, right) blocks: left <- 1 + <-right blocks because it waits for right to get a value. See "do golang channels maintain order".
All channels created here are unbuffered channels.

All 10000 goroutines are created.

Point B: right and left are declared, using the short variable declaration.

  • right is initialized to leftmost, but it doesn't matter, because it will be reassigned to a new channel in the for loop (right = make(chan int)).
    Another way to declare right would have been:

    var right chan int
    
  • left is initialized with leftmost, the very first channel created.

Point A: But once that channel start waiting (left <- 1 + <-right), the for loop set left to right, and created a new right: that is how the daisy chain is build

 left <- (new) right (now left) <- (new) right (now left) <- ...

Then, one value is sent to the last right channel created: {c <- 1 }(right)

And you wait for the first leftmost channel created to receive its value (incremented 10000 time).

Since receivers always block until there is data to receive, the main() function itself doesn't exit before leftmost finally receive its value.
If main() exited too soon, the daisy chain wouldn't have time to complete.