Go 中通过通道传递的消息是否保证是非阻塞的?

发布于 2024-11-24 18:28:05 字数 683 浏览 2 评论 0原文

为了评估 go 是否是音频/视频应用程序的可能选项,我想知道 go 中传递的消息是否满足任何非阻塞进度保证(无阻塞、无锁或无等待)。特别是,以下场景是相关的:

单个生产者单个消费者

两个线程使用共享通道进行通信。线程A只做异步发送,线程B只做异步接收。假设操作系统调度程序决定在“最糟糕的时刻”无限期地中断线程 A。线程 B 是否保证在有限数量的 cpu 周期内完成接收操作,或者线程 A 是否有(理论上)可能将通道置于线程 B 需要等待操作系统恢复线程 A 的状态?

多个生产者

多个线程 A1、A2、A3...使用共享通道与一个或多个其他线程进行通信。线程 Ai 仅执行异步发送。假设 A2、A3...在“最糟糕的时刻”被操作系统调度程序无限期地挂起。线程 A1 是否仍能保证在有限数量的 cpu 周期内完成发送操作?进一步假设每个线程只想发送一次。如果程序运行足够长的时间(使用“恶意”调度程序可能会导致某些线程挨饿或在“最糟糕的时刻”中断并恢复线程),是否至少可以保证一次发送成功?

我对这里的典型场景不太感兴趣,而是对最坏情况的保证感兴趣。 有关阻塞、锁定和等待的更多详细信息,请参阅非阻塞算法(维基百科) -免费算法。

In order to assess whether go is a possible option for an audio/video application, I would like to know whether message passing in go satisfies any non-blocking progress guarantees (being obstruction-free, lock-free or wait-free). In particular, the following scenarios are relevant:

Single producer single consumer:

Two threads communicate using a shared channel. Thread A only does asynchronous sends, thread B only does asynchronous receives. Suppose the OS scheduler decides to interrupt thread A at the "worst possible moment" for an indefinite amount of time. Is thread B guaranteed to finish a receive operation in a bounded number of cpu cycles or is there a (theoretical) possibility that thread A can put the channel into a state where thread B needs to wait for the OS to resume thread A?

Multiple producers:

Several threads A1, A2, A3, ... communicate with one or more others threads using a shared channel. The threads Ai only do asynchronous sends. Suppose A2, A3, ... are suspended by the OS scheduler at the "worst possible moment" for an indefinite amount of time. Is thread A1 still guaranteed to finish a send operation in a bounded number of cpu cycles? Suppose further that each thread only wants to do one send. If the program is run sufficiently long (with a "malicious" scheduler which potentially starves some threads or interrupts and resumes threads at the "worst possible moment"), is at least one send guaranteed to succeed?

I am not so much interested in typical scenarios here, but rather worst-case guarantees.
See Non-blocking algorithm (Wikipedia) for more details on obstruction-, lock- and wait-free algorithms.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

天赋异禀 2024-12-01 18:28:05

根据定义,正常发送和接收是阻塞操作。您可以使用 select 语句进行非阻塞发送或接收:(

select {
case ch <- msg:
default:
}

接收非常相似;只需替换 case 语句。)

仅当通道缓冲区中有空间时才会发生发送。否则运行默认情况。请注意,内部仍然使用互斥锁(如果我正确阅读代码)。

Normal sends and receives are blocking operations by definition. You can do a non-blocking send or receive by using a select statement:

select {
case ch <- msg:
default:
}

(Receiving is very similar; just replace the case statement.)

The send only takes place when there is room in the channel's buffer. Otherwise the default case runs. Note that internally a mutex is still used (if I'm reading the code correctly).

夏尔 2024-12-01 18:28:05

Go内存模型不要求发送和接收是非阻塞的,并且当前的< a href="http://golang.org/src/pkg/runtime/chan.c#L173" rel="nofollow">运行时 实现锁定send的通道接收。这意味着,例如,如果操作系统调度程序中断正在运行另一个 go 例程的另一个线程,而该线程在已获取通道锁的情况下尝试在同一通道上发送或接收,则可能会导致发送或接收 go 例程挨饿。 。

所以不幸的是答案是:(

(除非有人使用非阻塞算法重新实现部分运行时)。

The Go Memory Model doesn't require sends and receives to be non-blocking, and the current runtime implementation locks the channel for send and recv. This means, for instance, that it is possible to starve a sending or receiving go-routine if the OS-scheduler interrupts another thread running another go-routine which tries to send or receive on the same channel while it has already acquired the channel's lock.

So the answer is unfortunately no :(

(unless someone reimplement parts of the runtime using non-blocking algorithms).

治碍 2024-12-01 18:28:05

您询问是否保证一个操作在有限数量的周期内完成,这当然不是该语言(或大多数底层操作系统)的设计考虑因素。

如果在单线程中运行,则 Go 在 goroutine 之间使用协作多任务。因此,如果一个例程永远不会屈服,那么另一个例程将永远不会运行。如果您的程序在多个线程上运行(由 GOMAXPROCS 设置),那么您可以同时运行多个 goroutine,在这种情况下,操作系统控制线程之间的调度。然而,在这两种情况下,函数调用的完成时间都没有保证上限。

请注意,goroutine 的协作性质使您可以对调度执行进行一定程度的控制——也就是说,例程永远不会被抢占。在您屈服之前,您将保留对线程的控制权。

至于阻塞行为,请参阅语言规范

容量(以元素数量表示)设置通道中缓冲区的大小。如果容量大于零,则通道是异步的:如果缓冲区未满(发送)或不为空(接收),则通信操作会成功而不会阻塞,并且元素按照发送的顺序接收。如果容量为零或不存在,则只有当发送方和接收方都准备就绪时,通信才能成功。

请注意,通道上的非阻塞发送和接收可以使用已经提到的 select 语法来完成。

You're asking whether an operation is guarantee to complete within a bounded number of cycles, which of course is not a design consideration for this language (or most underlying OSes).

If run in a single thread, then Go uses cooperative multitasking between goroutines. So if one routine never yields, then the other will never run. If your program runs on multiple threads (as set by GOMAXPROCS), then you can run several goroutines simultaneously, in which case the OS controls scheduling between the threads. However, in neither case is there a guaranteed upper bound on the time to completion for a function call.

Note that the cooperative nature of goroutines gives you some amount of control over scheduling execution -- that is, routines are never preempted. Until you yield, you retain control of the thread.

As for blocking behavior, see The language specification:

The capacity, in number of elements, sets the size of the buffer in the channel. If the capacity is greater than zero, the channel is asynchronous: communication operations succeed without blocking if the buffer is not full (sends) or not empty (receives), and elements are received in the order they are sent. If the capacity is zero or absent, the communication succeeds only when both a sender and receiver are ready.

Note that non-blocking sends and receives on channels can be accomplished using the select syntax already mentioned.

喜爱皱眉﹌ 2024-12-01 18:28:05

Goroutine 不拥有通道或通道上发送的值。因此,已经在通道上发送/正在发送值的 Goroutine 的执行状态不会影响其他 Goroutine 在该通道上发送或接收值的能力,除非通道的缓冲区已满,在这种情况下所有发送都会阻塞直到发生相应的接收,或者缓冲区为空,在这种情况下,所有接收都将阻塞,直到有相应的发送。

因为 goroutine 使用协作调度(它们必须屈服于调度程序,要么通过通道操作、系统调用,要么显式调用 runtime.Gosched()),所以 goroutine 不可能在“最糟糕的时刻”被打断。 Goroutine 有可能永远不会屈服,在这种情况下,它可能会无限期地占用线程。如果你只有一个执行线程,那么你的其他 goroutine 将永远不会被调度。 Goroutine 永远不会被调度是可能的,但从统计角度来看是不可能的。然而,如果除了一个 Goroutine 之外的所有 Goroutine 在发送或接收时都被阻塞,则必须调度剩余的 Goroutine。

有可能陷入僵局。如果我有两个 goroutine 正在执行:

func Goroutine(ch1, ch2 chan int) {
   i := <-ch1
   ch2 <- i
}
...
ch1, ch2 := make(chan int), make(chan int)
go Goroutine(ch1, ch2)
go Goroutine(ch2, ch1)

那么,很明显,两个 goroutine 都在等待对方发送一个值,而这永远不会发生。

Goroutines do not own channels or the values sent on them. So, the execution status of a goroutine that has sent / is sending values on a channel has no impact on the ability of other goroutines to send or receive values on that channel, unless the channel's buffer is full, in which case all sends will block until a corresponding receive occurs, or the buffer is empty, in which case all receives will block until there is a corresponding send.

Because goroutines use cooperative scheduling (they have to yield to the scheduler, either through a channel operation, a syscall, or an explicit call to runtime.Gosched()), it is impossible for a goroutine to be interrupted at the "worst possible time". It is possible for a goroutine to never yield, in which case, it could tie up a thread indefinitely. If you have only one thread of execution, then your other goroutines will never be scheduled. It is possible, but statistically improbable, for a goroutine to just never be scheduled. However, if all goroutines but one are blocked on sends or receives, then the remaining goroutine must be scheduled.

It is possible to get a deadlock. If I have two goroutines executing:

func Goroutine(ch1, ch2 chan int) {
   i := <-ch1
   ch2 <- i
}
...
ch1, ch2 := make(chan int), make(chan int)
go Goroutine(ch1, ch2)
go Goroutine(ch2, ch1)

Then, as should be apparent, both goroutines are waiting for the other to send a value, which will never happen.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文