Вызывают ли отложенные функции при получении SIGINT в 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
}
}
1 ответ
Обратите внимание, что если вы "установите" свой канал сигнала с signal.Notify()
поведение по умолчанию будет отключено. Это означает, что если вы сделаете это, for
петля в вашем fn()
Функция не будет прервана, она продолжит работать.
Поэтому, когда вы получаете значение на своем зарегистрированном канале, вы должны сделать это for
цикл завершается, так что вы можете сделать "чистую" очистку. Остальные ресурсы 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
и завершает приложение. Этот код также должен ждать, пока все goroutines завершатся изящно. Для этого вы можете использовать sync.WaitGroup
: добавьте 1 к нему, когда вы запускаете программу, которую нужно ждать при выходе, и вызываете 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 через 3 секунды после его запуска:
working...
working...
working...
^Ccleaning up...