猿问

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


四季花海
浏览 264回答 3
3回答

摇曳的蔷薇

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

慕尼黑5688855

这是另一种解决方案-用于range读取频道。此代码将正确地交付给调度程序,并且在关闭通道时也将正确终止。func processevents(list chan func()) {&nbsp; &nbsp; for a := range list{&nbsp; &nbsp; &nbsp; &nbsp; a()&nbsp; &nbsp; }}

POPMUISE

好消息,自Go 1.2(2013年12月)以来,原始程序现在可以按预期运行。您可以在Playground上尝试一下。Go 1.2发行说明的“调度程序中的抢占”部分对此进行了说明:在以前的版本中,永远循环的goroutine可能会使同一线程上的其他goroutine饿死,当GOMAXPROCS仅提供一个用户线程时,这是一个严重的问题。在Go 1.2中,部分解决了此问题:调度程序在输入函数时偶尔被调用。
随时随地看视频慕课网APP

相关分类

Go
我要回答