【问题标题】:Why is this Go code blocking?为什么这个 Go 代码会阻塞?
【发布时间】:2012-09-13 19:33:48
【问题描述】:

我写了以下程序:

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 goroutine


    【解决方案1】:

    更新(Go 版本 1.2+)

    从 Go 1.2 开始,调度程序的工作原理是抢先式多任务。 这意味着原始问题中的问题(以及下面提供的解决方案)不再相关。

    来自Go 1.2 release notes

    调度器中的抢占

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

    简答

    它不会阻止写入。它卡在processevents 的无限循环中。 这个循环永远不会屈服于调度程序,导致所有 goroutine 无限期地锁定。

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

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

    长答案

    在 Go1.0.2 中,Go 的调度程序按照Cooperative multitasking 的原则工作。 这意味着它通过让这些例程在特定条件下与调度程序交互来将 CPU 时间分配给在给定 OS 线程中运行的各种 goroutine。 这些“交互”发生在 goroutine 中执行某些类型的代码时。 在 Go 中,这涉及到执行某种 I/O、系统调用或内存分配(在某些情况下)。

    在空循环的情况下,不会遇到这样的情况。因此,只要该循环正在运行,就永远不允许调度程序运行其调度算法。因此,这会阻止它将 CPU 时间分配给等待运行的其他 goroutine,并且您观察到的结果随之而来:您有效地创建了一个无法被调度程序检测或打破的死锁。

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

    for {
        runtime.Gosched()
    }
    

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

    【讨论】:

    • 所以GOMAXPROCS默认设置为1?
    • 您声明“从 Go 1.2 开始,调度程序的工作原理是抢先式多任务。这意味着原始问题中的问题(以及下面提出的解决方案)不再相关。这对我来说似乎不是真的。导致循环仍然从不调用非内联函数。
    【解决方案2】:

    这是另一种解决方案 - 使用 range 从频道读取。此代码将正确地让给调度程序,并在通道关闭时正确终止。

    func processevents(list chan func()) {
        for a := range list{
            a()
        }
    }
    

    【讨论】:

      【解决方案3】:

      好消息,从 Go 1.2(2013 年 12 月)开始,原始程序现在可以按预期运行。 你可以try it on Playground

      这在Go 1.2 release notes,“调度程序中的抢占”部分进行了解释:

      在以前的版本中,永远循环的 goroutine 可能会饿死 在同一线程上排除其他 goroutines,这是一个严重的问题 GOMAXPROCS 只提供了一个用户线程。在 Go 1.2 中,这部分是 解决:调度程序偶尔会在进入一个 功能。

      【讨论】:

      • 在 Playground 上,程序以“[进程耗时太长] 程序退出。”而终止。
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2017-07-14
      • 1970-01-01
      • 2020-10-30
      • 2015-11-03
      • 1970-01-01
      • 2011-10-16
      相关资源
      最近更新 更多