对于以下代码段,当收到^ C时,不会进行延迟呼叫。清理是否可能引入竞争条件?如果是,那么在收到中断时有什么更好的清除方式?
func fn() { // some code defer cleanup() go func() { c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) // Block until a signal is received. _ = <-c cleanup() } for { // Infinite loop. Returns iff an error is encountered in the // body } }
请注意,如果使用来“安装”信号通道signal.Notify(),则默认行为将被禁用。这意味着如果您这样做,函数中的for循环fn()将不会中断,它将继续运行。
signal.Notify()
for
fn()
因此,当您在注册的频道上收到一个值时,必须使该for循环终止,以便进行“清除”清除。否则,cleanup()应该释放的资源可能仍在使用中for,很可能导致错误或死机。
cleanup()
完成此操作后,您甚至不必cleanup()手动调用,因为从中返回fn()将正确运行延迟的函数。
这是一个例子:
var shutdownCh = make(chan struct{}) func fn() { defer cleanup() go func() { c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) <-c close(shutdownCh) }() for { select { case <-shutdownCh: return // Other cases might be listed here.. default: } time.Sleep(time.Millisecond) } }
当然,以上示例不保证应用程序终止。您应该具有一些侦听shutdownCh和终止应用程序的代码。此代码还应等待所有goroutine正常完成。为此,您可以使用sync.WaitGroup:在启动应在退出WaitGroup.Done()时等待的goroutine时向其添加1,并在此类goroutine完成时调用。
shutdownCh
sync.WaitGroup
WaitGroup.Done()
同样,由于在实际应用中可能有很多这样的信号处理,因此信号处理应移到“中央”位置,而不是在每个位置都进行。
这是一个完整的示例,该如何做:
var shutdownCh = make(chan struct{}) var wg = &sync.WaitGroup{} func main() { wg.Add(1) go func() { defer wg.Done() fn() }() c := make(chan os.Signal, 1) signal.Notify(c, os.Interrupt) <-c close(shutdownCh) wg.Wait() } func fn() { defer cleanup() for { select { case <-shutdownCh: return // Other cases might be listed here.. default: } fmt.Println("working...") time.Sleep(time.Second) } } func cleanup() { fmt.Println("cleaning up...") }
这是上面的应用CTRL+C在启动后三秒钟按的示例输出:
CTRL+C
working... working... working... ^Ccleaning up...