如何退出执行延迟呼叫的go程序?


76

我需要用来defer释放使用C库手动创建的分配,但os.Exit在某些时候我还需要处于非0状态。棘手的部分是os.Exit跳过任何延迟的指令:

package main

import "fmt"
import "os"

func main() {

    // `defer`s will _not_ be run when using `os.Exit`, so
    // this `fmt.Println` will never be called.
    defer fmt.Println("!")
    // sometimes ones might use defer to do critical operations
    // like close a database, remove a lock or free memory

    // Exit with status code.
    os.Exit(3)
}

游乐场:从https://gobyexample.com/exit盗取的http://play.golang.org/p/CDiAh9SXRM

那么如何退出执行已声明的defer调用的go程序呢?有其他选择os.Exit吗?



@ctcherry延期未成功os.Exitplay.golang.org/p/IsSI9VB7j8
marcio


哦,我知道了,这是个问题。就我而言,我只能os.Exit在运行其他也会延迟某些操作的操作之后才进行延迟操作……让我想一想。
marcio 2014年

defermain联合os.Exit绝对是一个粗糙的斑点。像下面的@Rob Napier一样围绕它进行架构是一种更好的方法。
ctcherry 2014年

Answers:


28

runtime.Goexit() 是实现这一目标的简单方法。

Goexit终止调用它的goroutine。没有其他goroutine受到影响。Goexit在终止goroutine之前运行所有延迟的调用。但是,由于Goexit并不慌张,因此这些延迟函数中的任何恢复调用都将返回nil。

然而:

从主goroutine调用Goexit会终止该goroutine,而不会返回func main。由于func main尚未返回,因此程序将继续执行其他goroutine。如果所有其他goroutine退出,程序将崩溃。

因此,如果您从主goroutine调用它,则main需要在顶部添加

defer os.Exit(0)

在此之下,您可能想要添加一些其他defer语句来通知其他goroutine停止和清理。


1
我不知道runtime.Goexit()存在。这是最近发布的吗?
marcio

2
@marcio我在Go存储库中进行了一些挖掘。我找不到确切的引入时间,但是在发布此问题之前,我确实找到了引用了该测试的测试,并且为“ Copyright 2013”
EMBLEM

2
@marcio我做了一些进一步的挖掘,找到了2009年文档的存档。Goexit在那里列出。
EMBLEM

我将其标记为已接受的答案。其他答案肯定仍然有效,但这似乎是最简单的方法-目前:D
marcio

42

只需将程序下移一个级别并返回退出代码即可:

package main

import "fmt"
import "os"

func doTheStuff() int {
    defer fmt.Println("!")

    return 3
}

func main() {
    os.Exit(doTheStuff())
}

所以我不应该使用defer内部func main或任何退出的功能?
marcio 2014年

4
更重要的是,我不建议os.Exit()在代码中随意使用。除了错误代码的问题之外,这使得测试非常困难。@ctcherry链接的peterSO的解决方案是可以的,但无法将IMO很好地扩展到更大的程序。您将必须走向code全球。我相信您应该保持main()相当简单,并只照顾操作系统级别的事情(例如最终状态代码)。
Rob Napier 2014年

嗨,我找到了一种无需施加任何体系结构即可解决此问题的方法。无论如何,+ 1是因为始终尝试KISS是一个很好的建议。
marcio

28

经过一番研究,参考,我找到了一个替代方案:

我们可以利用panicrecover。事实证明panic,从本质上讲,它将接受defer调用,但也始终会以非0状态代码退出并转储堆栈跟踪。诀窍是我们可以使用以下方法覆盖恐慌行为的最后一个方面:

package main

import "fmt"
import "os"

type Exit struct{ Code int }

// exit code handler
func handleExit() {
    if e := recover(); e != nil {
        if exit, ok := e.(Exit); ok == true {
            os.Exit(exit.Code)
        }
        panic(e) // not an Exit, bubble up
    }
}

现在,要在任何时候退出程序并仍然保留任何已声明的defer指令,我们只需要发出一个Exit类型:

func main() {
    defer handleExit() // plug the exit handler
    defer fmt.Println("cleaning...")
    panic(Exit{3}) // 3 is the exit code
}

除了在其中插入一行外,不需要任何重构func main

func main() {
    defer handleExit()
    // ready to go
}

这可以在较大的代码库中很好地扩展,因此我将其留待仔细检查。希望能帮助到你。

游乐场:http//play.golang.org/p/4tyWwhcX0-


迄今为止最好的答案!一个问题,那如何处理正常退出呢?如何确保handleExit即使在正常退出情况下也能被get调用?参考:play.golang.org/p/QDJum4kOXk取消注释该注释,//panic并查看其中的区别。
xpt

我认为不存在用于正常退出0事件的句柄,但是您始终可以紧急(Exit {0})然后进行处理,或者可以在main()上的其他任何事件之前推迟handleNormalExit()?
marcio

16

对于后代来说,对我来说这是一个更优雅的解决方案:

func main() { 
    retcode := 0
    defer func() { os.Exit(retcode) }()
    defer defer1()
    defer defer2()

    [...]

    if err != nil {
        retcode = 1
        return
    }
}

1
这确实是其他答案中最好的部分。
PRMan

0

我建议使用goroutine

func main(){
     fmt.Println("Hello world")
     go func(){
          os.Exit(0)
     }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.