单个通道上的多个接收器。谁会收到数据?

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

Multiple receivers on a single channel. Who gets the data?

问题

无缓冲通道会阻塞接收器,直到通道上有数据可用。对于同一个通道上的多个接收器(例如在使用 goroutine 时),我不清楚这种阻塞行为是如何发生的。我确定只要该通道上没有发送数据,它们都会一直阻塞。
但是,一旦我向该通道发送一个值,会发生什么呢?哪个接收器/ goroutine 将获得数据并解除阻塞?是全部接收器?还是排在第一位的接收器?还是随机选择一个?

英文:

Unbuffered channels block receivers until data is available on the channel. It's not clear to me how this blocking behaves with multiple receivers on the same channel (say when using goroutines). I am sure they would all block as long as there is no data sent on that channel.
But what happens once I send a single value to that channel? Which receiver/goroutine will get the data and therefore unblock? All of them? The first in line? Random?

答案1

得分: 17

一个随机的(非确定性的)接收操作将会被执行。

参见语言规范(https://golang.org/ref/spec#Select_statements):

"select" 语句的执行分为几个步骤:

  1. 对于语句中的所有 case,接收操作的通道操作数以及发送操作的通道和右侧表达式会在进入 "select" 语句时按照源代码顺序被精确地评估一次。结果是一组要接收或发送的通道,以及要发送的相应值。在该评估中的任何副作用都会发生,无论选择了哪个(如果有的话)通信操作来继续执行。带有短变量声明或赋值的 RecvStmt 的左侧表达式尚未被评估。
  2. **如果有一个或多个通信操作可以继续执行,将通过均匀的伪随机选择来选择一个可以继续执行的操作。**否则,如果有一个默认 case,将选择该 case。如果没有默认 case,则 "select" 语句将阻塞,直到至少有一个通信操作可以继续执行。
  3. 除非选择的 case 是默认 case,否则将执行相应的通信操作。
  4. 如果选择的 case 是带有短变量声明或赋值的 RecvStmt,则会评估左侧表达式,并将接收到的值(或值)赋给它们。
  5. 执行所选 case 的语句列表。
英文:

A single random (non-deterministic) one will receive it.

See the language spec:

> Execution of a "select" statement proceeds in several steps:
>
> 1. For all the cases in the statement, the channel operands of receive operations and the channel and right-hand-side expressions of send
> statements are evaluated exactly once, in source order, upon entering
> the "select" statement. The result is a set of channels to receive
> from or send to, and the corresponding values to send. Any side
> effects in that evaluation will occur irrespective of which (if any)
> communication operation is selected to proceed. Expressions on the
> left-hand side of a RecvStmt with a short variable declaration or
> assignment are not yet evaluated.
> 2. If one or more of the communications can proceed, a single one that can proceed is chosen via a uniform pseudo-random selection.
> Otherwise, if there is a default case, that case is chosen. If there
> is no default case, the "select" statement blocks until at least one
> of the communications can proceed.
> 3. Unless the selected case is the default case, the respective communication operation is executed.
> 4. If the selected case is a RecvStmt with a short variable declaration or an assignment, the left-hand side expressions are
> evaluated and the received value (or values) are assigned.
> 5. The statement list of the selected case is executed.

答案2

得分: 5

默认情况下,goroutine之间的通信是“同步的”和“无缓冲的”:只有在有接收者准备接收值时,发送操作才会完成。必须有一个接收者准备从通道接收数据,然后发送方可以直接将其交给接收者。

因此,通道的发送/接收操作会阻塞,直到另一侧准备就绪为止:

  1. 通道上的发送操作会阻塞,直到同一通道上有接收者可用:如果ch上没有接收者接收值,就无法将其他值放入通道中。反之亦然:当通道不为空时,无法发送新值到ch中!因此,发送操作将等待,直到ch再次可用。

  2. 通道的接收操作会阻塞,直到同一通道上有发送者可用:如果通道中没有值,接收者会被阻塞。

下面的示例说明了这一点:

package main
import "fmt"

func main() {
    ch1 := make(chan int)
    go pump(ch1) // pump hangs
    fmt.Println(<-ch1) // 仅打印0
}

func pump(ch chan int) {
    for i := 0; ; i++ {
        ch <- i
    }
}

由于没有接收者,goroutine会挂起并仅打印第一个数字。

为了解决这个问题,我们需要定义一个新的goroutine,在无限循环中从通道中读取数据。

func receive(ch chan int) {
    for {
        fmt.Println(<-ch)
    }
}

然后在main()函数中:

func main() {
    ch := make(chan int)
    go pump(ch)
    receive(ch)
}
英文:

By default the goroutine communication is synchronous and unbuffered: sends do not complete until there is a receiver to accept the value. There must be a receiver ready to receive data from the channel and then the sender can hand it over directly to the receiver.

So channel send/receive operations block until the other side is ready:

1. A send operation on a channel blocks until a receiver is available for the same channel: if there’s no recipient for the value on ch, no other value can be put in the channel. And the other way around: no new value can be sent in ch when the channel is not empty! So the send operation will wait until ch becomes available again.

2. A receive operation for a channel blocks until a sender is available for the same channel: if there is no value in the channel, the receiver blocks.

This is illustrated in the below example:

package main
import &quot;fmt&quot;

func main() {
	ch1 := make(chan int)
	go pump(ch1) // pump hangs
	fmt.Println(&lt;-ch1) // prints only 0
}

func pump(ch chan int) {
	for i:= 0; ; i++ {
		ch &lt;- i
	}
}

Because there is no receiver the goroutine hangs and print only the first number.

To workaround this we need to define a new goroutine which reads from the channel in an infinite loop.

func receive(ch chan int) {
	for {
		fmt.Println(&lt;- ch)
	}
}

Then in main():

func main() {
	ch := make(chan int)
	go pump(ch)
	receive(ch)
}

答案3

得分: 0

如果程序允许多个goroutine在一个通道上接收数据,那么发送者就是广播。每个接收者都应该能够平等地处理数据。因此,Go运行时使用什么机制来决定哪个goroutine接收者运行并不重要,参见https://github.com/golang/go/issues/247。但是,如果通道是无缓冲的,每个发送的数据项只会被一个接收者处理。

英文:

If the program is allowing multiple goroutines to receive on a single channel then the sender is broadcasting. Each receiver should be equally able to process the data. So it does not matter what mechanism the go runtime uses to decide which of the many goroutine receivers will run Cf. https://github.com/golang/go/issues/247. But only ONE will run for each sent item if the channel is unbuffered.

答案4

得分: 0

这里这里有一些关于这个问题的讨论。

但是在Go内存模型中确立了最多只会有一个匹配。

> 每个特定通道上的发送操作都会与该通道上的相应接收操作匹配,通常在不同的goroutine中进行。

这并不像我希望的那样明确,但稍后他们给出了一个信号量实现的示例:

var limit = make(chan int, 3)

func main() {
	for _, w := range work {
		go func(w func()) {
			limit <- 1
			w()
			// 如果有多个通道可以从单个发送操作接收,
			// 那么可能会释放多个“锁”,使其成为无效的信号量实现
			<-limit
		}(w)
	}
	select{}
}
英文:

There have been some discussion about this

But what is established in the Go Memory Model is that it will be at most one of them.

> Each send on a particular channel is matched to a corresponding receive from that channel, usually in a different goroutine.

That isn't as clear cut as I would like, but later down they give this example of a semaphore implementation

var limit = make(chan int, 3)

func main() {
	for _, w := range work {
		go func(w func()) {
			limit &lt;- 1
			w()
			// if it were possible for more than one channel to receive
			// from a single send, it would be possible for this to release
			// more than one &quot;lock&quot;, making it an invalid semaphore
			// implementation
			&lt;-limit
		}(w)
	}
	select{}
}

huangapple
  • 本文由 发表于 2015年7月21日 21:02:50
  • 转载请务必保留本文链接:https://go.coder-hub.com/31539804.html
匿名

发表评论

匿名网友

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

确定