为什么这是Go代码阻塞?

为什么这是Go代码阻塞?,go,goroutine,Go,Goroutine,我编写了以下程序: 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 proc

我编写了以下程序:

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!")
    }
}
主程序包
进口(
“fmt”
)
func processevents(list chan func()){
为了{
//a:=更新(Go版本1.2+)
从Go 1.2开始,调度程序的工作原理是“先发制人”多任务处理。
这意味着原始问题中的问题(以及下面给出的解决方案)不再相关

调度程序中的抢占

在以前的版本中,一个永远循环的goroutine可能会饿死其他goroutine 在同一个线程上,当GOMAXPROCS只提供一个用户线程时,这是一个严重的问题。 在Go>1.2中,部分解决了这一问题:调度程序偶尔会在 函数的入口。这意味着任何包含(非内联)函数的循环 调用可以被抢占,允许其他goroutine在同一线程上运行

简短回答 它没有阻塞写操作。它卡在
processevents
的无限循环中。 此循环从不向调度程序屈服,导致所有goroutine无限期锁定

如果您注释掉对
processevents
的调用,您将得到预期的结果,直到第100次写入。此时程序会恐慌,因为没有人从通道读取

另一个解决方案是在循环中调用
runtime.Gosched()

长话短说 在Go1.0.2中,Go的调度程序是基于。 这意味着它通过让这些例程在特定条件下与调度程序交互,将CPU时间分配给在给定OS线程内运行的各种Goroutine。 在goroutine中执行某些类型的代码时,会发生这些“交互”。 在go的例子中,这涉及到某种类型的I/O、系统调用或内存分配(在某些情况下)

在空循环的情况下,不会遇到此类情况。因此,只要该循环在运行,调度程序就永远不允许运行其调度算法。因此,这会阻止它将CPU时间分配给其他等待运行的goroutine,从而导致您观察到的结果:您实际上创建了死锁t计划程序无法检测到hat或将其破坏

Go中通常不需要空循环,在大多数情况下,空循环表示程序中存在错误。如果出于任何原因需要空循环,则必须在每次迭代中通过调用
runtime.Gosched()
手动向调度程序屈服

for {
    runtime.Gosched()
}

GOMAXPROCS
设置为值
>1
是一种解决方案。虽然这将消除您观察到的直接问题,但如果计划程序决定将循环goroutine移动到自己的操作系统线程(即),它将有效地将问题移动到另一个操作系统线程。除非您调用
runtime.LockOSThread()
processevents
函数的开头。即使如此,我仍然不会依赖此方法作为一个好的解决方案。只需调用
runtime.Gosched()
在循环本身中,将解决所有问题,无论goroutine在哪个操作系统线程中运行。

这里是另一个解决方案-使用
范围
从通道读取。此代码将正确地向调度程序屈服,并在通道关闭时正确终止

func processevents(list chan func()) {
    for a := range list{
        a()
    }
}
好消息是,自Go 1.2(2013年12月)以来,原来的程序现在按预期运行。 你可以

“调度程序中的抢占”一节对此进行了解释:

在以前的版本中,一个永远循环的goroutine可能会饿死 在同一线程上执行其他goroutine,当 GOMAXPROCS只提供了一个用户线程 addressed(已寻址):调度程序偶尔在进入 功能


因此,GOMAXPROCS设置为1作为默认值?您声明“从Go 1.2开始,调度程序按照先发制人的多任务处理原则工作。这意味着原始问题中的问题(以及下面介绍的解决方案)是对我来说,这似乎不是真的。因为循环仍然从不调用非内联函数。在游乐场上,程序以“[进程花费的时间太长]程序退出”结束