2015-10-20 37 views
0

有什麼辦法可以退出Go程序,但執行所有掛起的延遲語句嗎?清除臨時文件的最佳方法

我一直在使用延遲清理臨時文件,但當程序被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 
    } 
} 
+0

不,但你可以重構你的代碼,所以它可以優雅地關閉,然後推遲執行。我建議在main的頂部聲明'c'並將它傳遞給go例程。在for循環中,你需要一個select語句來監聽'c',如果你得到一個信號,就停止你正在做的事情並返回。然後你不需要使用'os.Exit'來殺死你的goroutines(因爲你從來不應該這麼做),你的程序可以正常返回,從而允許你的被攻擊的os.RemoveAll進行清理。 – evanmcdonnal

回答

1

從golang參考:

A 「推遲」 語句調用一個函數其執行被推遲到 的那一刻周邊功能返回

當您調用os.Exit(0)時,您會繞過正常返回過程,並且不會執行延期的功能。

此外,即使推遲在主要goroutine內部工作,其他goroutine中的推遲也不起作用,因爲他們會在返回之前死亡。

更好的代碼架構可以讓你得到類似的東西。您需要將您長期運行的流程視爲員工。調出工人的每個長時間運行過程並在調用工人後立即推遲清理。使用選擇在主夠程信號捕獲和同步工作

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 
} 

這是選擇一個快速和骯髒的方式來處理兩個工人,一個更好的辦法是使用sync.WaitGroup

+1

比我寫出來的時間寫得更好的答案。儘管如此,搞清楚很有趣。爲了完整起見,您可以在程序退出時從規範中添加關於[非主要例程](https://golang.org/ref/spec#Program_execution)會發生什麼的引號。 [os.Exit()](https://golang.org/pkg/os/#Exit)同上「程序立即終止;延遲函數不運行。」 – AndrewN

0

我會建議不依賴於延遲,而是定義可用於延遲或信號塊中的可重用函數。這樣的事情:

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