在 Go 中收到 SIGINT 时是否调用延迟函数?
Are deferred functions called when SIGINT is received in Go?
对于下面的代码片段,在收到 ^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
}
}
请注意,如果您 "install" 您的信号通道 signal.Notify()
,默认行为将被禁用。这意味着如果你这样做,你的 fn()
函数中的 for
循环将不会被中断,它会继续 运行.
因此,当您在注册频道上收到一个值时,您必须终止 for
循环,以便进行 "clean" 清理。否则应该释放的资源 cleanup()
可能仍在 for
中使用,很可能导致错误或恐慌。
执行此操作后,您甚至不必手动调用 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
:当你启动一个应该在退出时等待的 goroutine 时向它加 1,并在这样的 goroutine 完成时调用 WaitGroup.Done()
。
此外,由于在真实的应用程序中可能会有很多这样的信号处理,因此信号处理应移至 "central" 位置,而不是在每个位置都完成。
这是一个完整的示例:
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...")
}
这是上述应用程序的示例输出,在启动 3 秒后按 CTRL+C:
working...
working...
working...
^Ccleaning up...
对于下面的代码片段,在收到 ^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
}
}
请注意,如果您 "install" 您的信号通道 signal.Notify()
,默认行为将被禁用。这意味着如果你这样做,你的 fn()
函数中的 for
循环将不会被中断,它会继续 运行.
因此,当您在注册频道上收到一个值时,您必须终止 for
循环,以便进行 "clean" 清理。否则应该释放的资源 cleanup()
可能仍在 for
中使用,很可能导致错误或恐慌。
执行此操作后,您甚至不必手动调用 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
:当你启动一个应该在退出时等待的 goroutine 时向它加 1,并在这样的 goroutine 完成时调用 WaitGroup.Done()
。
此外,由于在真实的应用程序中可能会有很多这样的信号处理,因此信号处理应移至 "central" 位置,而不是在每个位置都完成。
这是一个完整的示例:
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...")
}
这是上述应用程序的示例输出,在启动 3 秒后按 CTRL+C:
working...
working...
working...
^Ccleaning up...