英文:
Order of Goroutine Unblocking on Single Channel
问题
Goroutines在通道上阻塞的顺序决定了它们解除阻塞的顺序吗?我不关心发送的消息的顺序(它们保证是有序的),而是关心解除阻塞的Goroutines的顺序。
假设有一个空的通道ch
,被多个Goroutines(1、2和3)共享,每个Goroutine都试图在ch
上接收一条消息。由于ch
是空的,每个Goroutine都会被阻塞。当我向ch
发送一条消息时,Goroutine 1会首先解除阻塞吗?还是2或3可能会接收到第一条消息?(或者反过来,Goroutines试图发送)
我有一个playground,似乎表明Goroutines阻塞的顺序就是它们解除阻塞的顺序,但我不确定这是否是由于实现而导致的未定义行为。
英文:
Does order in which the Goroutines block on a channel determine the order they will unblock? I'm not concerned with the order of the messages that are sent (they're guaranteed to be ordered), but the order of the Goroutines that'll unblock.
Imagine a empty Channel ch
shared between multiple Goroutines (1, 2, and 3), with each Goroutine trying to receive a message on ch
. Since ch
is empty, each Goroutine will block. When I send a message to ch
, will Goroutine 1 unblock first? Or could 2 or 3 possibly receive the first message? (Or vice-versa, with the Goroutines trying to send)
I have a playground that seems to suggest that the order in which Goroutines block is the order in which they are unblocked, but I'm not sure if this is an undefined behavior because of the implementation.
答案1
得分: 4
这是一个很好的问题,它涉及到并发设计时的一些重要问题。根据当前的实现,对于你的具体问题,答案是基于FIFO(先进先出)的。除非实施者认为LIFO(后进先出)在某些情况下更好,否则不太可能改变。
然而,并没有保证。所以你应该避免编写依赖于特定实现的代码。
更广泛的问题涉及到非确定性、公平性和饥饿。
也许令人惊讶的是,在基于CSP(通信顺序进程)的系统中,非确定性并不是因为并行发生的。它是由于并发而可能发生,但并不是因为并发。相反,非确定性是在做出选择时产生的。在CSP的形式化代数中,这是以数学方式建模的。幸运的是,你不需要了解数学知识就能使用Go语言。但从形式上讲,两个goroutine可以并行执行,结果仍然可能是确定的,前提是所有的选择都被消除。
Go语言允许通过select
显式地引入非确定性,也可以通过共享通道的末端隐式地引入非确定性。如果你有点对点(一个读者,一个写者)的通道,第二种情况就不会出现。所以如果在特定情况下这很重要,你可以做出设计选择。
公平性和饥饿通常是同一个问题的两个相对方面。饥饿是那些动态问题(如死锁、活锁和竞态条件)之一,可能导致性能不佳,更可能导致错误行为。这些动态问题是无法测试的(更多信息),需要一定程度的分析来解决。显然,如果系统的一部分由于无法访问某些资源而无响应,那么就需要更大程度上的公平性来管理这些资源。
由于当前的FIFO行为,共享通道末端的访问可能会提供一定程度的公平性,这可能足够了。但如果你想要保证公平性(不考虑实现的不确定性),可以使用select
和一组点对点通道的数组。通过始终优先选择它们的顺序,可以轻松实现公平索引,将最后选择的放在堆栈的底部。这种解决方案可以保证公平性,但可能会带来一些性能损失。
(附注:参见位于英国坎特伯雷的研究人员关于Java虚拟机中公平性缺陷的有趣发现,这个问题从未得到解决!)
英文:
This is a good question - it touches on some important issues when doing concurrent design. As has already been stated, the answer to your specific question is, according to the current implementation, FIFO based. It's unlikely ever to be different, except perhaps if the implementers decided, say, a LIFO was better for some reason.
There is no guarantee, though. So you should avoid creating code that relies on a particular implementation.
The broader question concerns non-determinism, fairness and starvation.
Perhaps surprisingly, non-determinism in a CSP-based system does not come from things happening in parallel. It is possible because of concurrency, but not because of concurrency. Instead, non-determinism arises when a choice is made. In the formal algebra of CSP, this is modelled mathematically. Fortunately, you don't need to know the maths to be able to use Go. But formally, two goroutines code execute in parallel and the outcome could still be deterministic, provided all the choices are eliminated.
Go allows choices that introduce non-determinism explicitly via select
and implicitly via ends of channels being shared between goroutines. If you have point-to-point (one reader, one writer) channels, the second kind does not arise. So if it's important in a particular situation, you have a design choice you can make.
Fairness and starvation are typically opposite sides of the same coin. Starvation is one of those dynamic problems (along with deadlock, livelock and race conditions) that result perhaps in poor performance, more likely in wrong behaviour. These dynamic problems are un-testable (more on this) and need some level analysis to solve. Clearly, if part of a system is unresponsive because it is starved of access to certain resources, then there is a need for greater fairness in governing those resources.
Shared access to channel ends may well provide a degree of fairness because of the current FIFO behaviour and this may appear sufficient. But if you want it guaranteed (regardless of implementation uncertainties), it is possible instead to use a select
and a bundle of point-to-point channels in an array. Fair indexing is easy to achieve by always preferring them in an order that puts the last-selected at the bottom of the pile. This solution can guarantee fairness, but probably with a small performance penalty.
(aside: see "Wot No Chickens" for a somewhat-amusing discovery made by researchers in Canterbury, UK concerning a fairness flaw in the Java Virtual Machine - which has never been rectified!)
答案2
得分: 2
我相信这是未指定的,因为内存模型文档只是说:“对于一个通道的发送操作发生在对该通道的对应接收操作完成之前。”在规范的发送语句和接收操作符部分中,并没有说明哪个操作会先解除阻塞。目前,gc工具链在这里使用有序的FIFO队列来控制哪个goroutine会解除阻塞,但我在规范中没有看到它必须始终如此的承诺。
(只是一般背景说明,Playground代码在GOMAXPROCS=1的情况下运行,即在一个核心上运行,因此某些类型的并发相关的不确定性不会出现。)
英文:
I believe it's unspecified because the memory model document only says "A send on a channel happens before the corresponding receive from that channel completes." The spec sections on send statements and the receive operator don't say anything about what unblocks first. Right now the gc toolchain uses an orderly FIFO queue to control which goroutine unblocks, but I don't see any promises in the spec that it must always be so.
(Just for general background note that Playground code runs with GOMAXPROCS=1, i.e., on one core, so some types of concurrency-related unpredictability just won't come up.)
答案3
得分: 1
顺序没有指定,但当前的实现使用先进先出(FIFO)队列来等待 goroutine。
权威文档是 Go 内存模型。内存模型没有为两个 goroutine 发送到同一通道定义 happens-before 关系,因此顺序没有指定。接收也是如此。
英文:
The order is not specified, but current implementations use a FIFO queue for waiting goroutines.
The authoritative document is the Go Memory Model. The memory model does not define a happens-before relationship for two goroutines sending to the same channel, therefore the order is not specified. Ditto for receive.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论