È possibile acquisire un segnale Ctrl + C ed eseguire una funzione di pulizia, in modo "differito"?


207

Voglio catturare il segnale Ctrl+C( SIGINT) inviato dalla console e stampare alcuni totali della corsa parziale.

Questo è possibile a Golang?

Nota: quando ho pubblicato per la prima volta la domanda ero confuso di Ctrl+Cessere SIGTERMinvece di SIGINT.

Risposte:


260

È possibile utilizzare il pacchetto os / signal per gestire i segnali in arrivo. Ctrl+ Cè SIGINT , quindi puoi usarlo per intrappolare 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
    }
}()

Il modo in cui causi la chiusura del programma e la stampa delle informazioni dipende interamente da te.


Grazie! Quindi ... ^ C non è SIGTERM, allora? AGGIORNAMENTO: Siamo spiacenti, il link che hai fornito è abbastanza dettagliato!
— Sebastián Grignoli,

19
Invece di for sig := range g {, puoi anche usare <-sigchancome in questa risposta precedente: stackoverflow.com/questions/8403862/…
— Denys Séguret

3
@dystroy: Certo, se stai per terminare il programma in risposta al primo segnale. Usando il loop puoi catturare tutti i segnali se decidi di non terminare il programma.
— Lily Ballard,

79
Nota: è necessario creare effettivamente il programma affinché funzioni. Se si esegue il programma go runin una console e si invia un SIGTERM tramite ^ C, il segnale viene scritto nel canale e il programma risponde, ma sembra uscire improvvisamente dal ciclo. Questo perché anche SIGRERM lo fa go run! (Questo mi ha causato una notevole confusione!)
— William Pursell,

5
Si noti che affinché la goroutine ottenga il tempo del processore per gestire il segnale, la goroutine principale deve chiamare un'operazione di blocco o chiamare runtime.Goschedin un posto appropriato (nel ciclo principale del programma, se ne ha uno)
— misterbee

107

Questo funziona:

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
Per gli altri lettori: guarda la risposta di @adamonduty per una spiegazione del perché vuoi catturare os.Interrupt e syscall.SIGTERM Sarebbe stato bello includere la sua spiegazione in questa risposta, soprattutto perché ha pubblicato mesi prima di te.
— Chris,

1
Perché stai usando un canale non bloccante? È necessario?
— Alba

4
@Barry perché la dimensione del buffer è 2 anziché 1?
— pdeva,

2
Ecco un estratto dalla documentazione . "Il segnale del pacchetto non bloccherà l'invio a c: il chiamante deve assicurarsi che c abbia spazio di buffer sufficiente per tenere il passo con la velocità del segnale prevista. Per un canale utilizzato per la notifica di un solo valore di segnale, è sufficiente un buffer di dimensione 1".
— bmdelacruz,

25

Per aggiungere leggermente alle altre risposte, se vuoi catturare SIGTERM (il segnale predefinito inviato dal comando kill), puoi usare syscall.SIGTERMal posto di os.Interrupt. Attenzione che l'interfaccia di syscall è specifica del sistema e potrebbe non funzionare ovunque (ad es. Su Windows). Ma funziona bene per catturare entrambi:

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

7
La signal.Notifyfunzione consente di specificare più segnali contemporaneamente. Pertanto, è possibile semplificare il codice signal.Notify(c, os.Interrupt, syscall.SIGTERM).
— jochen

Penso di averlo scoperto dopo aver pubblicato. Fisso!
— adamlamar,

2
Che dire di os.Kill?
— Alba

2
@Eclipse Ottima domanda! os.Kill corrisponde a syscall.Kill, che è un segnale che può essere inviato ma non catturato. È equivalente al comando kill -9 <pid>. Se vuoi catturare kill <pid>e spegnere con grazia, devi usare syscall.SIGTERM.
— adamlamar,

@adamlamar Ahh, ha senso. Grazie!
— Alba

18

C'erano (al momento della pubblicazione) uno o due piccoli errori di battitura nella risposta accettata sopra, quindi ecco la versione pulita. In questo esempio sto arrestando il profiler della CPU quando ricevo 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
Si noti che affinché la goroutine ottenga il tempo del processore per gestire il segnale, la goroutine principale deve chiamare un'operazione di blocco o chiamare runtime.Goschedin un posto appropriato (nel ciclo principale del programma, se ne ha uno)
— misterbee


0

Death è una semplice libreria che utilizza i canali e un gruppo di attesa per attendere i segnali di spegnimento. Una volta che il segnale è stato ricevuto, chiamerà quindi un metodo di chiusura su tutte le strutture che si desidera pulire.


3
Così tante righe di codice e una dipendenza da libreria esterna per fare ciò che si può fare in quattro righe di codice? (Come da risposta accettata)
— Jacob

ti permette di fare la pulizia di tutti in parallelo e di chiudere automaticamente le strutture se hanno l'interfaccia di chiusura standard.
— Ben Aldrich,

0

Puoi avere una diversa goroutine che rileva i segnali syscall.SIGINT e syscall.SIGTERM e li inoltra a un canale usando signal.Notify . Puoi inviare un hook a quel goroutine usando un canale e salvarlo in una sezione di funzioni. Quando viene rilevato il segnale di arresto sul canale, è possibile eseguire quelle funzioni nella sezione. Può essere utilizzato per ripulire le risorse, attendere il completamento delle goroutine, conservare i dati o stampare i totali delle esecuzioni parziali.

Ho scritto una piccola e semplice utility per aggiungere ed eseguire hook all'arresto. Spero possa essere di aiuto.

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

Puoi farlo in modo "differito".

esempio per spegnere un server con garbo:

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

Questa è un'altra versione che funziona nel caso abbiate delle attività da ripulire. Il codice lascerà il processo di pulizia nel loro metodo.

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
}

nel caso in cui sia necessario pulire la funzione principale, è necessario acquisire il segnale nel thread principale utilizzando anche go func ().


-2

Solo per la cronaca se qualcuno ha bisogno di un modo per gestire i segnali su Windows. Avevo l'obbligo di gestire da prog A chiamando prog B tramite os / exec ma prog B non è mai stato in grado di terminare con grazia perché inviando segnali tramite ex. cmd.Process.Signal (syscall.SIGTERM) o altri segnali non sono supportati su Windows. Il modo in cui ho gestito è creando un file temporaneo come segnale ex. .signal.term tramite prog A e prog B devono verificare se quel file esiste su base intervallo, se il file esiste uscirà dal programma e gestirà una pulizia se necessario, sono sicuro che ci sono altri modi ma questo ha fatto il lavoro.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.