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

时间:2016-01-19 18:42:59

标签: go goroutine

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

这是简单的代码

package main

import "os"

func main() {
    for {
        go func() { os.Exit(0) }()
    }
}

我的问题:

  1. 为什么os.Exit不会终止goroutine?
  2. 终止(停止)goroutine执行的正确方法是什么?
  3. 游乐场:http://play.golang.org/p/GAeOI-1Ksc

3 个答案:

答案 0 :(得分:10)

你遇到了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() {}()
    }
}

如果你在Go Playground上运行它,它应该可以工作(事实上,here's a link)。

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

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

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

答案 1 :(得分:0)

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

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

等待任意数量的goroutine完成的常用方法是使用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")

答案 2 :(得分:-4)

实施死手或终止开关

package main

import (
        "fmt"
        "time"
        "os"
)

const maxNoTickle = 50          // will bail out after this many no tickles
const maxWorking = 20           // pretendWork() will tickle this many times
const deadTicks = 250           // milliseconds for deadHand() to check for tickles
const reportTickles = 4         // consecutive tickles or no tickles to print something

var (
        tickleMe bool           // tell deadHand() we're still alive
        countNoTickle int       // consecutive no tickles
        countGotTickle int      // consecutive tickles
)

/**
*       deadHand() - callback to kill program if nobody checks in after some period
*/
func deadHand() {
        if !tickleMe {
                countNoTickle++
                countGotTickle = 0
                if countNoTickle > maxNoTickle {
                        fmt.Println("No tickle max time reached. Bailing out!")
                        // panic("No real panic. Just checking stack")
                        os.Exit(0)
                }
                if countNoTickle % reportTickles == 0 {
                        // print dot for consecutive no tickles
                        fmt.Printf(".")
                }
        } else {
                countNoTickle = 0
                countGotTickle++
                tickleMe = false        // FIXME: might have race condition here
                if countGotTickle % reportTickles == 0 {
                        // print tilda for consecutive tickles
                        fmt.Printf("~")
                }
        }
        // call ourselves again
        time.AfterFunc(deadTicks * time.Millisecond, deadHand)
}

/**
*       init() - required to start deadHand
*/
func init() {
        time.AfterFunc(250 * time.Millisecond, deadHand)
        tickleMe = true
}

/**
*       pretendWork() - your stuff that does its thing
*/
func pretendWork() {
        for count := 0; count < maxWorking; count++ {
                tickleMe = true // FIXME: might have race condition here
                // print W pretending to be busy
                fmt.Printf("W")
                time.Sleep(100 * time.Millisecond)
        }
}

func main() {
        go workTillDone()
        for {
                // oops, program went loop-d-loopy
        }
}