清除临时文件的最佳方法

Is there any way to exit a Go program, but execute all the pending defer statements?

I've been clearing up temporary files by using defer, but the deferred statements aren't executed when the program is interrupted with Ctrl+C or even os.Exit.

After exiting this program with Ctrl+C, both foo.txt and bar.txt are left over:

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
    }
}

From golang reference:

A "defer" statement invokes a function whose execution is deferred to the moment the surrounding function returns

When you call os.Exit(0) you bypass the normal return procedure and your deferred functions are not executed.

Also, even if the deferred worked inside the main goroutine, the defers in other goroutines would not work since they would die before returning.

A better code architecture would allow you to get something similar. You need to think about your long running processes as workers. Export every long running process in workers and defer any clean up right after calling the worker. Use a select in the main goroutine to capture signals and synchronise work

package main

import (
    "fmt"
    "io/ioutil"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func check(e error) {
    if e != nil {
        panic(e)
    }
}

func main() {
    ioutil.WriteFile("./foo.txt", []byte("foo"), 0644)
    defer os.RemoveAll("./foo.txt")

    // Worker 1
    done := make(chan bool, 1)
    go func(done chan bool) {
        fmt.Println("worker 1 with bar ...")

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

        // various long running things
        time.Sleep(3 * time.Second)
        done <- true
    }(done)
    defer os.RemoveAll("./bar.txt")
    // End worker1

    s := make(chan os.Signal, 1)
    signal.Notify(s, os.Interrupt)
    signal.Notify(s, syscall.SIGTERM)

    // Worker 2
    done2 := make(chan bool, 1)
    go func(done chan bool) {
        fmt.Println("worker 2 ...")
        time.Sleep(6 * time.Second)
        done <- true
    }(done2)
    // End worker 2

    select {
    case <-s:
        fmt.Println("Quiting with signal - exit")
    case <-done:
        <-done2
    case <-done2:
        <-done
    }

    return
}

This select is a quick and dirty way to handle two workers, a better way would be to use sync.WaitGroup

I would recommend not relying on defer, but defining a reusable function that can be used in a defer or in the signal block. Something like this:

package main

import (
    "fmt"
    "io/ioutil"
    "os"
    "os/signal"
    "syscall"
)

func main() {
    ioutil.WriteFile("./foo.txt", []byte("foo"), 0644)
    cleanup := func(){
       os.RemoveAll("./foo.txt")
       os.RemoveAll("./bar.txt")
    }
    defer cleanup() //for normal return

    go func() {
        ioutil.WriteFile("./bar.txt", []byte("bar"), 0644)
        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.")
        cleanup()
        os.Exit(0)
    }()

    for {
        // various long running things
    }
}