清理临时文件的最佳方法

huangapple go评论69阅读模式
英文:

Best way to clear up temporary files

问题

有没有办法退出一个Go程序,但执行所有未完成的defer语句?

我一直在使用defer来清理临时文件,但是当程序被Ctrl+C中断或者使用os.Exit退出时,defer语句不会被执行。

在使用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 {
			// 各种长时间运行的操作
		}
	}()

	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 {
		// 各种长时间运行的操作
	}
}
英文:

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 (
	&quot;fmt&quot;
	&quot;io/ioutil&quot;
	&quot;os&quot;
	&quot;os/signal&quot;
	&quot;syscall&quot;
)

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

	go func() {
		ioutil.WriteFile(&quot;./bar.txt&quot;, []byte(&quot;bar&quot;), 0644)
		defer os.RemoveAll(&quot;./bar.txt&quot;)
		for {
			// various long running things
		}
	}()

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	signal.Notify(c, syscall.SIGTERM)
	go func() {
		&lt;-c
		fmt.Println(&quot;Received OS interrupt - exiting.&quot;)
		os.Exit(0)
	}()

	for {
		// various long running things
	}
}

答案1

得分: 1

从golang参考文档中可以得知:

> "defer"语句会调用一个函数,该函数的执行被延迟到包围函数返回的时刻。

当你调用os.Exit(0)时,会绕过正常的返回过程,你的延迟函数不会被执行。

此外,即使延迟函数在主goroutine中起作用,其他goroutine中的延迟函数也不会起作用,因为它们会在返回之前终止。

更好的代码架构可以让你实现类似的效果。你需要将长时间运行的进程视为工作线程。将每个长时间运行的进程导出为工作线程,并在调用工作线程后立即延迟任何清理操作。在主goroutine中使用select来捕获信号并同步工作。

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
}

这个select语句是一种快速而简单的处理两个工作线程的方法,更好的方法是使用sync.WaitGroup。

英文:

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 (
&quot;fmt&quot;
&quot;io/ioutil&quot;
&quot;os&quot;
&quot;os/signal&quot;
&quot;syscall&quot;
&quot;time&quot;
)
func check(e error) {
if e != nil {
panic(e)
}
}
func main() {
ioutil.WriteFile(&quot;./foo.txt&quot;, []byte(&quot;foo&quot;), 0644)
defer os.RemoveAll(&quot;./foo.txt&quot;)
// Worker 1
done := make(chan bool, 1)
go func(done chan bool) {
fmt.Println(&quot;worker 1 with bar ...&quot;)
ioutil.WriteFile(&quot;./bar.txt&quot;, []byte(&quot;bar&quot;), 0644)
// various long running things
time.Sleep(3 * time.Second)
done &lt;- true
}(done)
defer os.RemoveAll(&quot;./bar.txt&quot;)
// 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(&quot;worker 2 ...&quot;)
time.Sleep(6 * time.Second)
done &lt;- true
}(done2)
// End worker 2
select {
case &lt;-s:
fmt.Println(&quot;Quiting with signal - exit&quot;)
case &lt;-done:
&lt;-done2
case &lt;-done2:
&lt;-done
}
return
}

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

答案2

得分: 0

我建议不要依赖defer,而是定义一个可重用的函数,可以在defer或信号块中使用。类似这样:

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() //用于正常返回

	go func() {
		ioutil.WriteFile("./bar.txt", []byte("bar"), 0644)
		for {
			// 各种长时间运行的操作
		}
	}()

	c := make(chan os.Signal, 1)
	signal.Notify(c, os.Interrupt)
	signal.Notify(c, syscall.SIGTERM)
	go func() {
		<-c
		fmt.Println("接收到操作系统中断 - 退出。")
        cleanup()
		os.Exit(0)
	}()

	for {
		// 各种长时间运行的操作
	}
}
英文:

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 (
&quot;fmt&quot;
&quot;io/ioutil&quot;
&quot;os&quot;
&quot;os/signal&quot;
&quot;syscall&quot;
)
func main() {
ioutil.WriteFile(&quot;./foo.txt&quot;, []byte(&quot;foo&quot;), 0644)
cleanup := func(){
os.RemoveAll(&quot;./foo.txt&quot;)
os.RemoveAll(&quot;./bar.txt&quot;)
}
defer cleanup() //for normal return
go func() {
ioutil.WriteFile(&quot;./bar.txt&quot;, []byte(&quot;bar&quot;), 0644)
for {
// various long running things
}
}()
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
signal.Notify(c, syscall.SIGTERM)
go func() {
&lt;-c
fmt.Println(&quot;Received OS interrupt - exiting.&quot;)
cleanup()
os.Exit(0)
}()
for {
// various long running things
}
}

huangapple
  • 本文由 发表于 2015年10月20日 10:38:35
  • 转载请务必保留本文链接:https://go.coder-hub.com/33227063.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定