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

时间:2011-07-17 19:18:44

标签: go channel lock-free nonblocking

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

单一生产者单一消费者:

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

多个制作人:

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

我对这里的典型场景不太感兴趣,而是最坏情况保证。 有关阻塞,锁定和无等待算法的详细信息,请参阅Non-blocking algorithm (Wikipedia)

4 个答案:

答案 0 :(得分:9)

正常发送和接收是按定义阻塞操作。您可以使用select语句执行非阻塞发送或接收:

select {
case ch <- msg:
default:
}

(接收非常相似;只需替换案例陈述。)

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

答案 1 :(得分:4)

The Go Memory Model不要求发送和接收是非阻止的,当前runtime implementation会锁定sendrecv的频道。这意味着,例如,如果操作系统调度程序中断另一个运行另一个尝试在同一个通道上发送或接收的另一个线程,而它已经获得了通道的锁定,则可能会使发送或接收的例行程序饿死

所以答案很遗憾没有 :(

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

答案 2 :(得分:1)

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

如果在单个线程中运行,则Go在goroutines之间使用cooperative multitasking。所以如果一个例程永远不会产生,那么另一个例程永远不会运行如果你的程序在多个线程上运行(由GOMAXPROCS设置),那么你可以同时运行几个goroutine,在这种情况下,OS控制线程之间的调度。但是,在任何情况下,函数调用的完成时间都没有保证上限。

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

关于阻止行为,请参阅The language specification

  

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

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

答案 3 :(得分:0)

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

因为goroutines使用协作调度(它们必须通过通道操作,系统调用或对runtime.Gosched()的显式调用来屈服于调度程序),所以goroutine不可能在“最糟糕的时间“。 goroutine可能永远不会产生,在这种情况下,它可能无限期地占用一个线程。如果您只有一个执行线程,那么您的其他goroutine将永远不会被安排。有可能,但在统计上是不可能的,因为goroutine永远不会被安排。但是,如果除了一个goroutine在发送或接收时被阻止,则必须安排剩余的goroutine。

可能会陷入僵局。如果我有两个goroutines执行:

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都在等待另一个发送一个永远不会发生的值。