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

huangapple go评论72阅读模式
英文:

understand the code - Go concurrency pattern: Daisy Chain

问题

我正在学习Go语言的并发模式。

其中一个模式我不太确定的是:Daisy Chain(雏菊链)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: 这段代码是做什么的?
	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. '链式反应'直到c <- 1时才开始,当信号1发送到最右边的通道时,反应一直传递到最左边。打印出10001。

原因是Go通道会在接收到信号之前阻塞'读'操作。

  1. @Rick-777展示了如何使用类似数组的结构来更容易理解。由于每个Go协程的大小只有大约6k,创建10000个通道并不是一个坏主意。

  2. 我对Point B附近的通道初始化代码进行了一些清理。以下是源代码:http://play.golang.org/p/1kFYPypr0l

英文:

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 (
	&quot;fmt&quot;
)

func f(left, right chan int) {
	left &lt;- 1 + &lt;-right
}

func main() {
	const n = 10000

	leftmost := make(chan int)
	right := leftmost               //point B: what does these do ?
	left := leftmost

	for i := 0; i &lt; n; i++ {
		right = make(chan int)
		go f(left, right)
		left = right                //point A
	}
	go func(c chan int) { c &lt;- 1 }(right)  
	fmt.Println(&lt;-leftmost)
}

Conclusion:

  1. the flow of channel going from right to left. It is good practice to write
    func f(left chan&lt;- int, right &lt;-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

答案1

得分: 7

VonC已经给出了直接的答案。以下是一些进一步的说明。

稍微整理过的版本在playground中,不同之处在于作为参数传递的通道明确指定了它们的方向,即<-chanchan<-。这样做是一个好的实践,因为编译器可以帮助你捕捉更多的错误。

另一种等效的程序,它使用一个通道数组来实现n个goroutine的链式连接。这样可以使用更少的代码分配相同数量的通道。请参见playground

package main

import (
	"fmt"
)

func f(left chan<- int, right <-chan int) {
	left <- 1 + <-right
}

func main() {
	const n = 10000

	// 首先我们构建一个包含n+1个通道的数组,每个通道都是'chan int'
	var channels [n+1]chan int
	for i := range channels {
		channels[i] = make(chan int)
	}

	// 现在我们将n个goroutine连接成一个链
	for i := 0; i < n; i++ {
		go f(channels[i], channels[i+1])
	}

	// 在右端插入一个值
	go func(c chan<- int) { c <- 1 }(channels[n])

	// 从左端取出值
	fmt.Println(<-channels[0])
}

我希望你现在能够看到原始程序与这个程序的等效性。有一个小的区别:原始程序不创建任何通道数组,所以使用的内存稍微少一些。

英文:

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. &lt;-chan and chan&lt;-. 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 (
	&quot;fmt&quot;
)

func f(left chan&lt;- int, right &lt;-chan int) {
	left &lt;- 1 + &lt;-right
}

func main() {
	const n = 10000

    // first we construct an array of n+1 channels each being a &#39;chan int&#39;
	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 &lt; n; i++ {
		go f(channels[i], channels[i+1])
	}

    // insert a value into the right-hand end
	go func(c chan&lt;- int) { c &lt;- 1 }(channels[n])

    // pick up the value emerging from the left-hand end
	fmt.Println(&lt;-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.

答案2

得分: 5

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

在这里,每个go f(left, right)都会阻塞:left <- 1 + <-right会阻塞,因为它等待right接收到一个值。参见“do golang channels maintain order”。这里创建的所有通道都是无缓冲通道。

总共创建了10000个goroutine。

B点:使用短变量声明声明了rightleft

  • right被初始化为最左边的通道,但这并不重要,因为它会在for循环中被重新赋值为一个新的通道(right = make(chan int))。
    另一种声明right的方式可以是:

      var right chan int
    
  • left被初始化为leftmost,也就是第一个创建的通道。

A点:但是一旦该通道开始等待(left <- 1 + <-right),for循环就会将left设置为right,并创建一个新的right:这就是构建链式结构的方式

 left <- (new) right (现在是 left) <- (new) right (现在是 left) <- ...

然后,一个值被发送到最后创建的right通道:{c <- 1 }(right)

并且等待第一个创建的leftmost通道接收到它的值(增加了10000次)。

由于接收者总是阻塞直到有数据可接收,所以main()函数在leftmost最终接收到它的值之前不会退出。
如果main()函数退出得太早,链式结构就没有时间完成。

英文:

It illustrates you can generate a large number of goroutines.

Here, each go f(left, right) blocks: left &lt;- 1 + &lt;-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 &lt;- 1 + &lt;-right), the for loop set left to right, and created a new right: that is how the daisy chain is build

 left &lt;- (new) right (now left) &lt;- (new) right (now left) &lt;- ...

Then, one value is sent to the last right channel created: {c &lt;- 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.

答案3

得分: 1

我发现运行这个程序的干扰运行可以帮助理解它。

首先,在执行以下代码后:

leftmost := make(chan int)
right := leftmost
left := leftmost

leftmostleftright都引用同一个chan int

[chan int]
     |                 
left, leftmost, right

让我们对for循环运行几次。

i = 0

当我们刚进入for循环时,

[chan int]
     |                 
left, leftmost, right

执行right = make(chan int)go f(left, right)后。

[chan int] <-(+1)- [chan int]
     |                 |
left, leftmost        right

执行left = right

[chan int] <-(+1)- [chan int]
     |                 |
  leftmost        left, right

i = 1

当我们刚进入for循环时,

[chan int] <-(+1)- [chan int]
     |                 |
  leftmost        left, right

执行right = make(chan int)go f(left, right)后。

[chan int] <-(+1)- [chan int] <-(+1)- [chan int]
     |                 |                   |
  leftmost            left               right

执行left = right

[chan int] <-(+1)- [chan int] <-(+1)- [chan int]
     |                                     |
  leftmost                            left, right

我觉得两个循环足够看出模式:

  • 每次循环我们都创建一个新的chan int并将其附加到“chan int的链表”的末尾。
  • 因此,在n = 100000次循环后,我们创建了100000个新的chan int,而“chan int的链表”中的chan int数量将为100001
  • 100001chan int意味着每对相邻chan int之间有100000个间隔,每个间隔表示一个+1

在for循环之前,因为所有的chan int都作为接收器而没有传入值,所以所有的chan int都只会等待。

在for循环之后,我们执行go func(c chan int) { c <- 1 }(right),然后将1传递到“chan int的链表”中,并对值执行+1操作100000次,所以最终结果为leftmost将是100001

当我们将1传递到“chan int的链表”中时,情况将如下所示:

[chan int] <-(+1)- [chan int] <-(+1)- ...... <-(+1)- [chan int] <- 1
     |                                                   |
  leftmost                                           left, right

我创建了一个包含所有代码的LeetCode Playground。你可以在这里尝试它:https://leetcode.com/playground/gAa59fh3

英文:

I found dry-run this program could be really helpful to understand it.

At first, after executing

leftmost := make(chan int)
right := leftmost
left := leftmost

leftmost, left, and right are all referring to the same chan int

[chan int]
     |                 
left, leftmost, right

Let's run some iterations for the for-loop.

i = 0

When we just enter the for loop,

[chan int]
     |                 
left, leftmost, right

after executing right = make(chan int) and go f(left, right).

[chan int] &lt;-(+1)- [chan int]
     |                 |
left, leftmost        right

after executing left = right

[chan int] &lt;-(+1)- [chan int]
     |                 |
  leftmost        left, right

i = 1

When we just enter the for loop,

[chan int] &lt;-(+1)- [chan int]
     |                 |
  leftmost        left, right

after executing right = make(chan int) and go f(left, right).

[chan int] &lt;-(+1)- [chan int] &lt;-(+1)- [chan int]
     |                 |                   |
  leftmost            left               right

after executing left = right

[chan int] &lt;-(+1)- [chan int] &lt;-(+1)- [chan int]
     |                                     |
  leftmost                            left, right

I feel like two loops are enough to see the pattern:

  • Every loop we create a new chan int and append it at the end of the "linked list of chan int".
  • So after n = 100000 loops, we created 100000 new chan int, and the number of chan int in the "linked list of chan int will be 100001.
  • 100001 chan int means 100000 gaps between each pair of adjacent chan int, and each gap means one +1.

Before the for loop, because all chan int are acting as receivers and there is no pass-in value, so all chan int will just wait.

After the for loop, we execute go func(c chan int) { c &lt;- 1 }(right), then the 1 is passed into the "linked list of chan int" and perform +1 on the value for 100000 times, so the final result to the leftmost will be 100001.

Things will be like when we pass 1 into the "linked list of chan int":

[chan int] &lt;-(+1)- [chan int] &lt;-(+1)- ...... &lt;-(+1)- [chan int] &lt;- 1
     |                                                   |
  leftmost                                           left, right

I created a leetcode playground holding all the code. You could try it here (https://leetcode.com/playground/gAa59fh3).

huangapple
  • 本文由 发表于 2014年10月1日 14:49:39
  • 转载请务必保留本文链接:https://go.coder-hub.com/26135616.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定