go - 为什么这个 Go 代码会阻塞?

标签 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 processevents(eventlist)

    for {
        eventlist <- test
        fmt.Println("Hey!")
    }
}

由于 channel 事件列表是一个缓冲 channel ,我想我应该得到输出“嘿!”的 100 倍,但它只显示一次。我的错在哪里?

最佳答案

更新(Go 版本 1.2+)

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

来自 Go 1.2 release notes

Pre-emption in the scheduler

In prior releases, a goroutine that was looping forever could starve out other goroutines on the same thread, a serious problem when GOMAXPROCS provided only one user thread. In Go > 1.2, this is partially addressed: The scheduler is invoked occasionally upon entry to a function. This means that any loop that includes a (non-inlined) function call can be pre-empted, allowing other goroutines to run on the same thread.

简答

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

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

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

长答案

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

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

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

for {
    runtime.Gosched()
}

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

关于go - 为什么这个 Go 代码会阻塞?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/12413510/

相关文章:

go - 多个 goroutine 在一个 channel 上监听

design-patterns - Go - 为什么调度 goroutine background workers 也需要自己的 goroutine?

go - 在 go channel 上选择内部范围

algorithm - 使用 golang 在图中查找 Cliques

go - 如何停止可能正在运行或未运行的 goroutine?

go - 如何使 Go append 在范围循环内工作

go - 基于 goroutine/channel 的机制是否应该取代并发映射?

dictionary - golang struct concurrent read and write without Lock 也运行ok?

data-structures - 在 golang 中实现嵌套矩阵的惯用方式

windows - 加载dll失败。找不到指定的模块