如何退出一个尊重延期通话的go计划?

mar*_*cio 53 exit go deferred

我需要用来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)
}
Run Code Online (Sandbox Code Playgroud)

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

那么如何退出一个尊重已宣布defer呼叫的go计划呢?还有其他选择os.Exit吗?

Rob*_*ier 33

只需将程序向下移动一级并返回退出代码:

package main

import "fmt"
import "os"

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

    return 3
}

func main() {
    os.Exit(doTheStuff())
}
Run Code Online (Sandbox Code Playgroud)

  • 更重要的是,我不建议在代码中的随机位置使用`os.Exit()`.除了错误代码的问题,它使测试非常困难.@ctcherry链接的peterSO解决方案没问题,但它不能很好地扩展IMO到更大的程序.你必须使`code`全局化.我相信你应该保持main()相当简单,并且只需要处理操作系统级别的事情(比如最终的状态代码). (4认同)

mar*_*cio 22

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

我们可以利用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
    }
}
Run Code Online (Sandbox Code Playgroud)

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

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

除了插入一条线外,它不需要任何重构func main:

func main() {
    defer handleExit()
    // ready to go
}
Run Code Online (Sandbox Code Playgroud)

这可以很好地扩展到更大的代码库,因此我将其留待审查.希望能帮助到你.

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


EMB*_*LEM 20

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

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

然而:

从主goroutine调用Goexit会终止那个没有func main返回的goroutine.由于func main尚未返回,程序继续执行其他goroutine.如果所有其他goroutine退出,程序崩溃.

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

defer os.Exit(0)
Run Code Online (Sandbox Code Playgroud)

在下面你可能想要添加一些其他defer语句来通知其他goroutines停止和​​清理.

  • 我不知道 `runtime.Goexit()` 存在。这是最近发布的吗? (2认同)
  • @marcio我在Go存储库中进行了一些挖掘。我找不到确切的引入时间,但是我确实找到了[此引用它的测试并且是“ Copyright 2013”​​](https://github.com/golang/go/blob/53fd522c0db58f3bd75d85295f46bb06e8ab1a9b/test/fixedbugs/issue5963 (.go)。 (2认同)
  • @marcio我做了一些进一步的挖掘,发现了[2009年文档的存档](http://web.archive.org/web/20091215044631/http://golang.org/pkg/runtime)。Goexit在那里列出。 (2认同)

Mik*_*ier 8

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

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

    [...]

    if err != nil {
        retcode = 1
        return
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 这确实占据了其他答案中最好的部分。 (2认同)
  • 这段代码将抑制恐慌。另外,如果“retcode”未更改(例如在恐慌期间),它将始终返回 0(无错误)。如果您确定您的代码没有恐慌,那么您可能对此感到满意 (2认同)