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

我需要使用defer释放使用os.Exit库手动创建的分配,但在某些时候我还需要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)
}

游乐场:[http://play.golang.org/p/CDiAh9SXRM]从[https://gobyexample.com/exit]被盗

那么,如何退出执行已声明defer调用的go程序? os.Exit是否有替代方案?

marcio asked 2020-01-14T15:47:29Z
4个解决方案
32 votes

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

package main

import "fmt"
import "os"

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

    return 3
}

func main() {
    os.Exit(doTheStuff())
}
Rob Napier answered 2020-01-14T15:48:29Z
22 votes

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

  • 不像[https://stackoverflow.com/a/27629493/438563]那样强加某种架构
  • 不需要像[https://stackoverflow.com/a/24601700/438563]中的任何全局值

我们可以利用deferExit的优势。事实证明,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-]

marcio answered 2020-01-14T15:49:22Z
19 votes

defer是完成此操作的简便方法。

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

然而:

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

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

defer os.Exit(0)

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

EMBLEM answered 2020-01-14T15:48:10Z
8 votes

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

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

    [...]

    if err != nil {
        retcode = 1
        return
    }
}
Mike Gleason jr Couturier answered 2020-01-14T15:49:42Z
translate from https://stackoverflow.com:/questions/27629380/how-to-exit-a-go-program-honoring-deferred-calls