一尘不染

为什么此Go代码被阻止?

go

我编写了以下程序:

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倍的输出“嘿!”,但是它只显示一次。我的错误在哪里?


阅读 242

收藏
2020-07-02

共1个答案

一尘不染

更新(Go 1.2版或更高版本)

从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,从而导致观察到的结果:有效地创建了一个死锁,调度程序无法检测到该死锁。

空循环通常在Go中是不需要的,并且在大多数情况下,它将指示程序中的错误。如果出于某种原因确实需要它,则必须通过调用runtime.Gosched()每次迭代来手动让出给调度程序。

for {
    runtime.Gosched()
}

提到GOMAXPROCS将值设置> 1为解决方案。尽管这可以消除您所观察到的直接问题,但是如果调度程序决定将循环goroutine移动到自己的OS线程,则可以将问题有效地转移到其他OS线程。除非您runtime.LockOSThread()processevents函数的开头进行调用,否则无法保证。即使那样,我仍然不会依靠这种方法来解决问题。只需调用runtime.Gosched()循环本身,就可以解决所有问题,而不管goroutine在哪个OS线程中运行。

2020-07-02