我编写了以下程序:
package main import ( "fmt" ) func processevents(list chan func()) { for { //a := <-list //a() } } func test() { fmt.Println("Ho!") } func main() { eventlist := make(chan func(), 100) go processevents(eventlist) for { eventlist <- test fmt.Println("Hey!") } }
由于通道事件列表是一个缓冲通道,我想我应该获得100倍的输出“嘿!”,但是它只显示一次。我的错误在哪里?
从Go 1.2开始,调度程序基于 抢先式多任务处理 原则。这意味着原始问题(以及下面提供的解决方案)中的问题不再相关。
从Go 1.2发行说明中
调度程序中的抢占 在以前的版本中,永远循环的goroutine可能会使同一线程上的其他goroutine饿死,当GOMAXPROCS仅提供一个用户线程时,这是一个严重的问题。在Go> 1.2中,部分解决了此问题:调度程序在输入函数时偶尔被调用。这意味着任何包含(非内联的)函数调用的循环都可以被抢占,从而允许其他goroutine在同一线程上运行。
调度程序中的抢占
在以前的版本中,永远循环的goroutine可能会使同一线程上的其他goroutine饿死,当GOMAXPROCS仅提供一个用户线程时,这是一个严重的问题。在Go> 1.2中,部分解决了此问题:调度程序在输入函数时偶尔被调用。这意味着任何包含(非内联的)函数调用的循环都可以被抢占,从而允许其他goroutine在同一线程上运行。
它不会阻止写入。它卡在的无限循环中processevents。此循环永远不会屈服于调度程序,从而导致所有goroutine无限期锁定。
processevents
如果将对的调用注释掉processevents,您将得到预期的结果,直到第100次写入为止。此时程序会出现紧急情况,因为没有人从该通道读取数据。
另一种解决方案是runtime.Gosched()在循环中调用。
runtime.Gosched()
使用Go1.0.2,Go的调度程序可以基于协作多任务处理原理工作。这意味着,通过在特定条件下使这些例程与调度程序交互,它将CPU时间分配给给定OS线程中运行的各种goroutine。当在goroutine中执行某些类型的代码时,就会发生这些“交互”。在go的情况下,这涉及进行某种I / O,系统调用或内存分配(在某些情况下)。
在空循环的情况下,永远不会遇到这种情况。因此,只要循环正在运行,就永远不允许调度程序运行其调度算法。因此,这可以防止它将CPU时间分配给其他等待运行的goroutine,从而导致观察到的结果:有效地创建了一个死锁,调度程序无法检测到该死锁。
空循环通常在Go中是不需要的,并且在大多数情况下,它将指示程序中的错误。如果出于某种原因确实需要它,则必须通过调用runtime.Gosched()每次迭代来手动让出给调度程序。
for { runtime.Gosched() }
提到GOMAXPROCS将值设置> 1为解决方案。尽管这可以消除您所观察到的直接问题,但是如果调度程序决定将循环goroutine移动到自己的OS线程,则可以将问题有效地转移到其他OS线程。除非您runtime.LockOSThread()在processevents函数的开头进行调用,否则无法保证。即使那样,我仍然不会依靠这种方法来解决问题。只需调用runtime.Gosched()循环本身,就可以解决所有问题,而不管goroutine在哪个OS线程中运行。
GOMAXPROCS
> 1
runtime.LockOSThread()