Вызывают ли отложенные функции при получении SIGINT в Go? - PullRequest
0 голосов
/ 18 мая 2018

Для приведенного ниже фрагмента отложенный вызов не выполняется при получении ^ 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 Ответ

0 голосов
/ 18 мая 2018

Обратите внимание, что если вы "установите" свой канал сигнала с 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 и завершает работу приложения.Этот код также должен ждать, пока все изящные программы завершатся.Для этого вы можете использовать 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...
...