猿问

清理临时文件的最佳方法

有没有办法退出 Go 程序,但执行所有挂起的 defer 语句?


我一直在使用 defer 清理临时文件,但是当程序被 Ctrl+C 甚至 os.Exit 中断时,不会执行延迟的语句。


用 Ctrl+C 退出这个程序后,foo.txt 和 bar.txt 都留下了:


package main


import (

    "fmt"

    "io/ioutil"

    "os"

    "os/signal"

    "syscall"

)


func main() {

    ioutil.WriteFile("./foo.txt", []byte("foo"), 0644)

    defer os.RemoveAll("./foo.txt")


    go func() {

        ioutil.WriteFile("./bar.txt", []byte("bar"), 0644)

        defer os.RemoveAll("./bar.txt")

        for {

            // various long running things

        }

    }()


    c := make(chan os.Signal, 1)

    signal.Notify(c, os.Interrupt)

    signal.Notify(c, syscall.SIGTERM)

    go func() {

        <-c

        fmt.Println("Received OS interrupt - exiting.")

        os.Exit(0)

    }()


    for {

        // various long running things

    }

}


智慧大石
浏览 125回答 2
2回答

墨色风雨

从 golang 参考:“延迟”语句调用一个函数,该函数的执行被推迟到周围函数返回的那一刻当您调用 os.Exit(0) 时,您绕过了正常的返回过程,并且不会执行您的延迟函数。此外,即使 deferred 在主 goroutine 中工作,其他 goroutine 中的 defer 也不会工作,因为它们会在返回之前死亡。更好的代码架构可以让你得到类似的东西。您需要将长时间运行的流程视为工人。导出工作进程中的每个长时间运行的进程,并在调用工作进程后立即推迟任何清理工作。在主 goroutine 中使用 select 来捕获信号并同步工作package mainimport (&nbsp; &nbsp; "fmt"&nbsp; &nbsp; "io/ioutil"&nbsp; &nbsp; "os"&nbsp; &nbsp; "os/signal"&nbsp; &nbsp; "syscall"&nbsp; &nbsp; "time")func check(e error) {&nbsp; &nbsp; if e != nil {&nbsp; &nbsp; &nbsp; &nbsp; panic(e)&nbsp; &nbsp; }}func main() {&nbsp; &nbsp; ioutil.WriteFile("./foo.txt", []byte("foo"), 0644)&nbsp; &nbsp; defer os.RemoveAll("./foo.txt")&nbsp; &nbsp; // Worker 1&nbsp; &nbsp; done := make(chan bool, 1)&nbsp; &nbsp; go func(done chan bool) {&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println("worker 1 with bar ...")&nbsp; &nbsp; &nbsp; &nbsp; ioutil.WriteFile("./bar.txt", []byte("bar"), 0644)&nbsp; &nbsp; &nbsp; &nbsp; // various long running things&nbsp; &nbsp; &nbsp; &nbsp; time.Sleep(3 * time.Second)&nbsp; &nbsp; &nbsp; &nbsp; done <- true&nbsp; &nbsp; }(done)&nbsp; &nbsp; defer os.RemoveAll("./bar.txt")&nbsp; &nbsp; // End worker1&nbsp; &nbsp; s := make(chan os.Signal, 1)&nbsp; &nbsp; signal.Notify(s, os.Interrupt)&nbsp; &nbsp; signal.Notify(s, syscall.SIGTERM)&nbsp; &nbsp; // Worker 2&nbsp; &nbsp; done2 := make(chan bool, 1)&nbsp; &nbsp; go func(done chan bool) {&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println("worker 2 ...")&nbsp; &nbsp; &nbsp; &nbsp; time.Sleep(6 * time.Second)&nbsp; &nbsp; &nbsp; &nbsp; done <- true&nbsp; &nbsp; }(done2)&nbsp; &nbsp; // End worker 2&nbsp; &nbsp; select {&nbsp; &nbsp; case <-s:&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println("Quiting with signal - exit")&nbsp; &nbsp; case <-done:&nbsp; &nbsp; &nbsp; &nbsp; <-done2&nbsp; &nbsp; case <-done2:&nbsp; &nbsp; &nbsp; &nbsp; <-done&nbsp; &nbsp; }&nbsp; &nbsp; return}这个选择是处理两个工人的一种快速而肮脏的方式,更好的方法是使用 sync.WaitGroup

呼唤远方

我建议不要依赖 defer,而是定义一个可以在 defer 或信号块中使用的可重用函数。像这样的东西:package mainimport (&nbsp; &nbsp; "fmt"&nbsp; &nbsp; "io/ioutil"&nbsp; &nbsp; "os"&nbsp; &nbsp; "os/signal"&nbsp; &nbsp; "syscall")func main() {&nbsp; &nbsp; ioutil.WriteFile("./foo.txt", []byte("foo"), 0644)&nbsp; &nbsp; cleanup := func(){&nbsp; &nbsp; &nbsp; &nbsp;os.RemoveAll("./foo.txt")&nbsp; &nbsp; &nbsp; &nbsp;os.RemoveAll("./bar.txt")&nbsp; &nbsp; }&nbsp; &nbsp; defer cleanup() //for normal return&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; ioutil.WriteFile("./bar.txt", []byte("bar"), 0644)&nbsp; &nbsp; &nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; // various long running things&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; }()&nbsp; &nbsp; c := make(chan os.Signal, 1)&nbsp; &nbsp; signal.Notify(c, os.Interrupt)&nbsp; &nbsp; signal.Notify(c, syscall.SIGTERM)&nbsp; &nbsp; go func() {&nbsp; &nbsp; &nbsp; &nbsp; <-c&nbsp; &nbsp; &nbsp; &nbsp; fmt.Println("Received OS interrupt - exiting.")&nbsp; &nbsp; &nbsp; &nbsp; cleanup()&nbsp; &nbsp; &nbsp; &nbsp; os.Exit(0)&nbsp; &nbsp; }()&nbsp; &nbsp; for {&nbsp; &nbsp; &nbsp; &nbsp; // various long running things&nbsp; &nbsp; }}
随时随地看视频慕课网APP

相关分类

Go
我要回答