Можно ли захватить сигнал Ctrl + C и запустить функцию очистки в отложенном режиме?

Я хочу захватить сигнал Ctrl + C (SIGINT), отправленный с консоли, и распечатать некоторые частичные итоги прогона.

Возможно ли это на Голанге?

Примечание. Когда я впервые разместил вопрос, я был сбит с толку тем, что Ctrl + C был SIGTERM вместо SIGINT.


person Sebastián Grignoli    schedule 29.06.2012    source источник


Ответы (10)


Вы можете использовать пакет 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
    }
}()

Способ, которым вы завершаете свою программу и распечатываете информацию, полностью зависит от вас.

person Lily Ballard    schedule 29.06.2012
comment
Я вижу, вы просто вызываете Notify () вместо signal.Notify (). Это то же самое? - person Sebastián Grignoli; 30.06.2012
comment
@ SebastiánGrignoli: Ошибка транскрипции. Ввод кода напрямую в SO не всегда приводит к тому, что действительно может быть выполнено. Я исправлю это. - person Lily Ballard; 30.06.2012
comment
Ok. Я спросил, потому что я видел такой вызов (без префикса) везде в документации библиотек. Я думаю, они относятся к вызову функции из библиотек ... - person Sebastián Grignoli; 30.06.2012
comment
@ SebastiánGrignoli: внутри любого пакета вы можете вызывать другие функции в этом пакете с неприукрашенным именем (например, Notify()). Вне этого пакета вы должны использовать идентификатор пакета (например, signal.Notify()). Итак, если вы видите документацию, относящуюся к функции без идентификатора пакета, это относится к этой функции в текущем пакете. - person Lily Ballard; 30.06.2012
comment
Вместо for sig := range g { вы также можете использовать <-sigchan, как в предыдущем ответе: stackoverflow.com/questions/8403862/ - person Denys Séguret; 30.06.2012
comment
@dystroy: Конечно, если вы действительно собираетесь завершить программу по первому сигналу. Используя цикл, вы можете уловить все сигналы, если вы решите не завершать программу. - person Lily Ballard; 01.07.2012
comment
Примечание: вы должны создать программу, чтобы это работало. Если вы запускаете программу через go run в консоли и отправляете SIGTERM через ^ C, сигнал записывается в канал, и программа отвечает, но неожиданно выходит из цикла. Это потому, что SIGRERM также переходит к go run! (Это вызвало у меня существенное замешательство!) - person William Pursell; 18.11.2012
comment
Обратите внимание, что для того, чтобы горутина получила процессорное время для обработки сигнала, основная горутина должна вызвать операцию блокировки или вызвать runtime.Gosched в соответствующем месте (в основном цикле вашей программы, если он есть). - person misterbee; 04.08.2013
comment
Есть ли способ показать текущий стек строк, в котором сейчас находится программа? в моем случае было какое-то чтение io, которое я не знаю, в какой строке он застрял ... - person James Tan; 18.10.2016
comment
Если у вас все равно есть основной цикл, вы можете просто select {} на канале вместо использования отдельной горутины и вызова мистических runtime функций. - person Dmiters; 22.05.2020

Это работает:

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
    }
}
person Community    schedule 10.08.2013
comment
Для других читателей: посмотрите ответ @adamonduty, чтобы узнать, почему вы хотите поймать os.Interrupt и syscall.SIGTERM Было бы неплохо включить его объяснение в этот ответ, тем более, что он написал за несколько месяцев до вас. - person Chris; 07.11.2016
comment
Почему вы используете неблокирующий канал? Это необходимо? - person Awn; 06.04.2017
comment
@Barry, почему размер буфера 2 вместо 1? - person pdeva; 05.05.2017
comment
Вот выдержка из документации. Пакетный сигнал не будет блокировать отправку на c: вызывающий должен убедиться, что c имеет достаточно места в буфере, чтобы не отставать от ожидаемой скорости сигнала. Для канала, используемого для уведомления только об одном значении сигнала, достаточно буфера размера 1. - person bmdelacruz; 10.02.2018
comment
Этот код работает, но у меня есть вопрос. Если я изменю os.Exit(1) на os.Exit(0), а затем запустил echo $?, код выхода будет 1 вместо 0. - person jimouris; 09.04.2021

Чтобы немного добавить к другим ответам, если вы действительно хотите поймать SIGTERM (сигнал по умолчанию, отправляемый командой kill), вы можете использовать syscall.SIGTERM вместо os.Interrupt. Помните, что интерфейс системных вызовов зависит от системы и может работать не везде (например, в Windows). Но он отлично работает, чтобы поймать и то, и другое:

c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
....
person adamlamar    schedule 14.05.2013
comment
А как насчет os.Kill? - person Awn; 06.04.2017
comment
@Eclipse Отличный вопрос! os.Kill соответствует syscall.Kill, который является сигналом, который может быть отправлен, но не пойман. Эквивалент команды kill -9 <pid>. Если вы хотите поймать kill <pid> и корректно завершить работу, вы должны использовать syscall.SIGTERM. - person adamlamar; 08.04.2017

В принятом выше ответе были (на момент публикации) одна или две небольших опечатки, так что вот исправленная версия. В этом примере я останавливаю профилировщик ЦП при получении 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)                                                     
  }                                                                
}()    
person gravitron    schedule 24.09.2012
comment
Обратите внимание, что для того, чтобы горутина получила процессорное время для обработки сигнала, основная горутина должна вызвать операцию блокировки или вызвать runtime.Gosched в соответствующем месте (в основном цикле вашей программы, если он есть). - person misterbee; 04.08.2013

Кажется, что все вышеперечисленное работает при соединении, но на странице сигналов gobyexample есть действительно чистый и полный пример сигнала. захват. Стоит добавить в этот список.

package main

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

func main() {
    sigs := make(chan os.Signal, 1)
    done := make(chan bool, 1)

    signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)

    go func() {
        sig := <-sigs
        fmt.Println()
        fmt.Println(sig)
        done <- true
    }()

    fmt.Println("awaiting signal")
    <-done
    fmt.Println("exiting")
}

Источник: gobyexample.com/signals

person willscripted    schedule 20.02.2015

посмотрите пример

Когда мы запустим эту программу, она заблокируется в ожидании сигнала. Набрав ctrl-C (который терминал показывает как ^ C), мы можем послать сигнал SIGINT, заставляя программу печатать прерывание, а затем выходить.

сигнал. Notify регистрирует данный канал для получения уведомлений об указанных сигналах.

package main

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

func main() {

    sig := make(chan os.Signal, 1)
    done := make(chan bool, 1)

    signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)

    go func() {
        sig := <-sig
        fmt.Println()
        fmt.Println(sig)
        done <- true

        fmt.Println("ctrl+c")
    }()

    fmt.Println("awaiting signal")
    <-done
    fmt.Println("exiting")
}

обнаружение отмены HTTP-запроса



package main

import (
    "fmt"
    "net/http"
    "time"
)

func main() {

    mux := http.NewServeMux()
    mux.HandleFunc("/path", func(writer http.ResponseWriter, request *http.Request) {

        time.Sleep(time.Second * 5)

        select {
        case <-time.After(time.Millisecond * 10):

            fmt.Println("started")
            return
        case <-request.Context().Done():
            fmt.Println("canceled")
        }
    })

    http.ListenAndServe(":8000", mux)

}
person dılo sürücü    schedule 16.09.2020

У вас может быть другая горутина, которая обнаруживает сигналы syscall.SIGINT и syscall.SIGTERM и передает их на канал с помощью signal.Notify. Вы можете послать ловушку к этой горутине, используя канал, и сохранить его в функциональном срезе. Когда на канале обнаружен сигнал выключения, вы можете выполнять эти функции в слайсе. Это можно использовать для очистки ресурсов, ожидания завершения выполнения горутин, сохранения данных или печати итоговых результатов частичного выполнения.

Я написал небольшую и простую утилиту для добавления и запуска хуков при завершении работы. Надеюсь, это поможет.

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()
person Ankit Arora    schedule 09.07.2017

Это еще одна версия, которая работает, если у вас есть какие-то задачи по очистке. Код оставит процесс очистки в своем методе.

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 ().

person Hlex    schedule 20.11.2017

Death - это простая библиотека, которая использует каналы и группу ожидания для ожидания сигналов выключения. Как только сигнал будет получен, он вызовет метод close для всех ваших структур, которые вы хотите очистить.

person Ben Aldrich    schedule 24.03.2015
comment
Так много строк кода и зависимость от внешней библиотеки, чтобы сделать то, что можно сделать в четырех строках кода? (согласно принятому ответу) - person Jay; 14.10.2016
comment
он позволяет вам выполнять очистку всех из них параллельно и автоматически закрывать структуры, если они имеют стандартный интерфейс закрытия. - person Ben Aldrich; 18.10.2019

Просто для протокола, если кому-то нужен способ обработки сигналов в Windows.

У меня было требование обрабатывать из программы A, вызывающей программу B через os / exec, но программа B никогда не могла корректно завершиться, потому что отправка сигналов через cmd.Process.Signal(syscall.SIGTERM) или другие сигналы не поддерживаются в Windows.

Я решил эту проблему, создав временный файл в качестве сигнала. Например, программа A создает файл .signal.term, а программе B необходимо проверять, существует ли этот файл на основе интервалов. Если файл существует, он выйдет из программы и при необходимости выполнит очистку.

Я уверен, что есть другие способы, но это сработало.

person user212806    schedule 02.10.2017