Чи можливо знімати сигнал Ctrl + C і запускати функцію очищення "відкладати"?


207

Я хочу зафіксувати сигнал Ctrl+C( SIGINT), що надсилається з консолі, та роздрукувати деякі часткові підсумки виконання.

Чи можливо це в Голангу?

Примітка. Коли я вперше опублікував це питання, я збентежився Ctrl+C, SIGTERMа не замість цього SIGINT.

Відповіді:


260

Ви можете використовувати пакет os / signal для обробки вхідних сигналів. Ctrl+ Cє SIGINT , тому ви можете використовувати це для пастки os.Interrupt.

c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func(){
    for sig := range c {
        // sig is a ^C, handle it
    }
}()

Те, як ви змусите програму припиняти та друкувати інформацію, повністю залежить від вас.


Дякую! Отже ... ^ C це не СИГЕРМ, значить? ОНОВЛЕННЯ: На жаль, надане вами посилання є досить детальним!
Себастьян Гріньолі

19
Замість того for sig := range g {, ви можете також використовувати <-sigchanяк в цьому попередній відповіді: stackoverflow.com/questions/8403862 / ...
Денис Séguret

3
@dystroy: Звичайно, якщо ви насправді збираєтесь припинити програму у відповідь на перший сигнал. За допомогою циклу ви можете зафіксувати всі сигнали, якщо випадково вирішите не припиняти програму.
Лілі Баллард

79
Примітка. Ви повинні фактично створити програму, щоб вона працювала. Якщо ви запускаєте програму через go runконсоль і надсилаєте SIGTERM через ^ C, сигнал записується в канал і програма реагує, але, схоже, несподівано випадає з циклу. Це тому, що SIGRERM go runтак само іде ! (Це викликає у мене суттєву плутанину!)
Вільям Перселл

5
Зауважте, що для того, щоб у програмі було отримано час процесора на обробку сигналу, головна програма повинна викликати операцію блокування або зателефонувати runtime.Goschedу відповідне місце (у головному циклі програми, якщо у неї є)
misterbee

107

Це працює:

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"
    "time" // or "runtime"
)

func cleanup() {
    fmt.Println("cleanup")
}

func main() {
    c := make(chan os.Signal)
    signal.Notify(c, os.Interrupt, syscall.SIGTERM)
    go func() {
        <-c
        cleanup()
        os.Exit(1)
    }()

    for {
        fmt.Println("sleeping...")
        time.Sleep(10 * time.Second) // or runtime.Gosched() or similar per @misterbee
    }
}

1
Для інших читачів: Подивіться на відповідь @adamonduty для пояснення того, чому ви хочете зловити os.Interrupt і syscall.SIGTERM Було б добре включити його пояснення у цю відповідь, тим більше, що він розміщував місяці перед вами.
Кріс

1
Чому ви використовуєте неблокуючий канал? Це потрібно?
Аун

4
@Barry, чому розмір буфера 2 замість 1?
pdeva

2
Ось уривок із документації . "Сигнал пакета не блокує надсилання на c: абонент повинен переконатися, що c має достатньо буферного простору, щоб не відставати від очікуваної швидкості сигналу. Для каналу, який використовується для повідомлення лише одного значення сигналу, буфер розміром 1 достатній."
bmdelacruz

25

Щоб трохи додати інші відповіді, якщо ви дійсно хочете зловити SIGTERM (сигнал за замовчуванням, який надсилає команда kill), ви можете використовувати syscall.SIGTERMзамість os.Interrupt. Слідкуйте за тим, щоб інтерфейс syscall був специфічним для системи і може працювати не скрізь (наприклад, у Windows). Але це чудово працює, щоб зловити обидва:

c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
....

7
signal.NotifyФункція дозволяє вказати кілька сигналів одночасно. Таким чином, ви можете спростити свій код до signal.Notify(c, os.Interrupt, syscall.SIGTERM).
jochen

Я думаю, що це я дізнався після публікації. Виправлено!
adamlamar

2
Що з os.Kill?
Аун

2
@Eclipse Чудове запитання! os.Kill відповідає до syscall.Kill, який є сигналом , який може бути посланий , але не спійманий. Його еквівалент команді kill -9 <pid>. Якщо ви хочете зловити kill <pid>і витончено відключити, вам доведеться скористатися syscall.SIGTERM.
adamlamar

@adamlamar Ааа, це має сенс. Дякую!
Awn

18

У прийнятій відповіді вище (в момент публікації) були одні-дві маленькі помилки, тож ось очищена версія. У цьому прикладі я зупиняю процесор CPU при отриманні Ctrl+ C.

// capture ctrl+c and stop CPU profiler                            
c := make(chan os.Signal, 1)                                       
signal.Notify(c, os.Interrupt)                                     
go func() {                                                        
  for sig := range c {                                             
    log.Printf("captured %v, stopping profiler and exiting..", sig)
    pprof.StopCPUProfile()                                         
    os.Exit(1)                                                     
  }                                                                
}()    

2
Зауважте, що для того, щоб у програмі було отримано час процесора на обробку сигналу, головна програма повинна викликати операцію блокування або зателефонувати runtime.Goschedу відповідне місце (у головному циклі програми, якщо у неї є)
misterbee


0

Смерть - це проста бібліотека, яка використовує канали та групу очікування для очікування сигналів відключення. Як тільки сигнал буде отриманий, він зателефонує метод закриття всіх ваших структур, які ви хочете очистити.


3
Стільки рядків коду та зовнішньої бібліотечної залежності, щоб зробити те, що можна зробити в чотирьох рядках коду? (відповідно до прийнятої відповіді)
Яків

це дозволяє проводити очищення всіх з них паралельно і автоматично закривати структури, якщо вони мають стандартний інтерфейс закриття.
Бен Олдріх

0

У вас може бути інша goroutut, яка виявляє сигнали syscall.SIGINT та syscall.SIGTERM та передає їх на канал за допомогою сигналу. Повідомте . . Ви можете надіслати гачок до цієї програми за допомогою каналу та зберегти його у фрагменті функції. Коли на каналі буде виявлено сигнал відключення, ви можете виконати ці функції в зрізі. Це можна використовувати для очищення ресурсів, очікування закінчення роботи програм, збереження даних або друку часткових підсумків запуску.

Я написав невелику і просту утиліту для додавання та запуску гачків при відключенні. Сподіваюся, це може бути корисною.

https://github.com/ankit-arora/go-utils/blob/master/go-shutdown-hook/shutdown-hook.go

Це можна зробити "відкладати".

Приклад вимкнення сервера витончено:

srv := &http.Server{}

go_shutdown_hook.ADD(func() {
    log.Println("shutting down server")
    srv.Shutdown(nil)
    log.Println("shutting down server-done")
})

l, err := net.Listen("tcp", ":3090")

log.Println(srv.Serve(l))

go_shutdown_hook.Wait()

0

Це ще одна версія, яка працює у випадку, якщо у вас є певні завдання з очищення. Код залишить процес очищення в їх методі.

package main

import (
    "fmt"
    "os"
    "os/signal"
    "syscall"

)



func main() {

    _,done1:=doSomething1()
    _,done2:=doSomething2()

    //do main thread


    println("wait for finish")
    <-done1
    <-done2
    fmt.Print("clean up done, can exit safely")

}

func doSomething1() (error, chan bool) {
    //do something
    done:=make(chan bool)
    c := make(chan os.Signal, 2)
    signal.Notify(c, os.Interrupt, syscall.SIGTERM)
    go func() {
        <-c
        //cleanup of something1
        done<-true
    }()
    return nil,done
}


func doSomething2() (error, chan bool) {
    //do something
    done:=make(chan bool)
    c := make(chan os.Signal, 2)
    signal.Notify(c, os.Interrupt, syscall.SIGTERM)
    go func() {
        <-c
        //cleanup of something2
        done<-true
    }()
    return nil,done
}

у випадку, якщо вам потрібно очистити головну функцію, вам потрібно зафіксувати сигнал у головній потоці, використовуючи також go func ().


-2

Просто для запису, якщо комусь потрібен спосіб обробляти сигнали в Windows. У мене була вимога обробляти програму прог. Виклику прогу B через os / exec, але прога B ніколи не змогла граціозно припинитися, тому що передача сигналів через ex. cmd.Process.Signal (syscall.SIGTERM) або інші сигнали не підтримуються у Windows. Я працював шляхом створення тимчасового файлу як сигналу, наприклад. .signal.term через prog A та prog B потрібно перевірити, чи існує цей файл на інтервальній базі, якщо файл існує, він вийде з програми та обробить очищення, якщо потрібно, я впевнений, що є інші способи, але це зробило роботу.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.