Golang:为什么os.Exit在goroutines中不起作用

A L*_*A L 7 go goroutine

我有一个非常简单算法的研究计划.当成功来临时,goroutine应该通过os.Exit(0)关闭(结束).我等了一天,两天......什么?:)

这是简单的代码

package main

import "os"

func main() {
    for {
        go func() { os.Exit(0) }()
    }
}
Run Code Online (Sandbox Code Playgroud)

我的问题是:

  1. 为什么os.Exit不会终止goroutine?
  2. 什么是终止(停止)goroutine执行的正确方法?

游乐场:http://play.golang.org/p/GAeOI-1Ksc

jos*_*hlf 13

你已经遇到了Go调度程序的一个棘手的角落.答案是os.Exit 确实会导致整个过程退出,但是你采用它的方式,goroutines永远不会运行.

可能发生的是for循环不断向可用goroutine列表中添加新的goroutine,但由于整个进程只在一个OS线程中运行,因此Go调度程序从未实际调度过不同的goroutine,并且只是继续运行for循环没有运行你产生的任何goroutine.试试这个:

package main

import "os"

func main() {
    for {
        go func() { os.Exit(0) }()
        func() {}()
    }
}
Run Code Online (Sandbox Code Playgroud)

如果你在Go Playground上运行它,它应该工作(事实上,这是一个链接).

好吧,上面的代码在你的代码不起作用的事实应该是非常神秘的.这样做的原因是Go调度程序实际上是非抢占式的.这意味着除非给定的goroutine自愿决定给调度程序选择运行其他东西,否则没有别的东西可以运行.

现在显然你从来没有编写过包含命令的代码来给调度程序一个运行的机会.当编译代码时,Go编译器会自动将这些代码插入到代码中.以下是上述代码工作原理的关键:goroutine可能决定运行调度程序的一个时间是调用函数的时间.因此,通过添加func() {}()调用(显然什么都不做),我们允许编译器添加对调度程序的调用,从而为此代码提供了安排不同goroutine的机会.因此,其中一个产生的goroutine运行,调用os.Exit,并且进程退出.

编辑:如果编译器内联调用(或者,在这种情况下,完全删除它,因为它什么也不做),函数调用本身可能是不够的.runtime.Gosched()另一方面,保证工作.

  • 或者您可以将GOMAXPROCS更改为> 1,它将无需修改即可使用.当你发现GOMAXPROCS在操场上是1时非常明显. (2认同)
  • @AL,去吧很简单.当你使用goroutines和channel编写适当的惯用代码时,一切都会正常工作.但是表面上的简单内容很复杂.Golang简单性是关于运行时的复杂性以及它对您的影响.因此,当你找到像这样的边缘情况时,你必须深入挖掘以了解正在发生的事情.当你有一个没有函数调用且你的GOMAXPROCS = 1的无限循环时,死锁和goroutines饥饿是一个边缘情况.这是一个众所周知的,但如果不了解go调度程序的基础知识,就很难理解. (2认同)

Jim*_*imB 0

您可以通过从函数返回来终止 Goroutine。如果需要确保goroutine运行完成,则需要等待goroutine完成。这通常是通过sync.WaitGroup或通过通道同步 goroutine 来完成的。

在您的示例中,您首先需要确保不可能产生无限数量的 goroutine。因为新的 goroutine 之间没有同步点main,所以不能保证它们中的任何一个都会os.Exit在主循环运行时执行调用。

等待任意数量的 goroutine 完成的常用方法是使用 a sync.WaitGroup,这将确保它们在退出之前全部执行完毕main

wg := sync.WaitGroup{}
for i := 0; i < 10000; i++ {
    wg.Add(1)
    go func() { defer wg.Done() }()
}

wg.Wait()
fmt.Println("done")
Run Code Online (Sandbox Code Playgroud)