Bir Ctrl + C sinyali yakalamak ve bir temizleme işlevini “erteleme” şeklinde çalıştırmak mümkün müdür?


207

Konsoldan gönderilen Ctrl+C( SIGINT) sinyali yakalamak ve bazı kısmi çalışma toplamlarını yazdırmak istiyorum.

Golang'da bu mümkün mü?

Not: Soruyu ilk gönderdiğimde Ctrl+Colmak SIGTERMyerine kafam karıştı SIGINT.

Yanıtlar:


260

Gelen sinyalleri işlemek için os / sinyal paketini kullanabilirsiniz. Ctrl+ COlan SIGINT size tuzak için kullanabilir, böylece 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
    }
}()

Programınızın bilgileri sonlandırmasına ve yazdırmasına neden olma şekliniz tamamen size bağlıdır.


Teşekkürler! Yani ... ^ C SIGTERM değil mi? GÜNCELLEME: Üzgünüz, sağladığınız bağlantı yeterince ayrıntılı!
Sebastián Grignoli

19
Bunun yerine, bu önceki for sig := range g {<-sigchan
yanıttaki

3
@dystroy: Elbette, ilk sinyale yanıt olarak programı gerçekten sonlandıracaksanız. Eğer karar gerçekleşmesi durumunda döngü kullanarak, tüm sinyalleri yakalamak değil programı sonlandıracaktır.
Lily Ballard

79
Not: Bunun çalışması için programı gerçekten oluşturmalısınız. Programı go runbir konsolda çalıştırırsanız ve ^ C üzerinden bir SIGTERM gönderirseniz, sinyal kanala yazılır ve program yanıt verir, ancak beklenmedik bir şekilde döngüden çıktığı görülür. Bunun nedeni SIGRERM'in de devam etmesidir go run! (Bu bana büyük karışıklığa neden oldu!)
William Pursell

5
Goroutinin sinyali işlemek için işlemci süresine sahip olması için, ana goroutinin bir engelleme işlemini çağırması veya runtime.Goscheduygun bir yerde (varsa programınızın ana döngüsünde)
çağırması gerektiğini unutmayın

107

Bu çalışıyor:

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
Diğer okuyucular için: Neden os.Interrupt ve syscall.SIGTERM'i yakalamak istediğinize dair bir açıklama için @adamonduty'nin cevabına bakın.
Chris

1
Neden engellemeyen bir kanal kullanıyorsunuz? Bu gerekli mi?
Tente

4
@Barry neden arabellek boyutu 1 yerine 2?
pdeva

2
İşte dokümantasyondan bir alıntı . "Paket sinyali c'ye gönderimi engellemeyecektir: arayan, c'nin beklenen sinyal hızına ayak uyduracak yeterli tampon alanına sahip olmasını sağlamalıdır. Sadece bir sinyal değerinin bildirimi için kullanılan bir kanal için 1 büyüklüğünde bir tampon yeterlidir."
bmdelacruz

25

Diğer cevaplara hafifçe eklemek için, aslında SIGTERM'i (kill komutu tarafından gönderilen varsayılan sinyal) yakalamak istiyorsanız syscall.SIGTERMos.Interrupt yerine kullanabilirsiniz . Syscall arabiriminin sisteme özel olduğundan ve her yerde çalışmayabileceğine dikkat edin (örn. Pencerelerde). Ancak her ikisini de yakalamak iyi çalışır:

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

7
signal.NotifyFonksiyon aynı anda birden sinyallerini belirlemenizi sağlar. Böylece, kodunuzu basitleştirebilirsiniz signal.Notify(c, os.Interrupt, syscall.SIGTERM).
jochen

Sanırım bunu gönderdikten sonra öğrendim. Sabit!
adamlamar

2
Os.Kill ne olacak?
Avn

2
@ Elips Harika bir soru! os.Kill karşılık için syscall.Killgönderilen ancak yakalanmayan edilebilir bir sinyal olan. Komuta eşdeğerdir kill -9 <pid>. Yakalamak kill <pid>ve incelikle kapatmak istiyorsanız, kullanmanız gerekir syscall.SIGTERM.
adamlamar

@adamlamar Ahh, bu mantıklı. Teşekkürler!
Avn

18

Yukarıdaki kabul edilen cevapta (gönderme sırasında) bir veya iki küçük yazım hatası vardı, bu yüzden temizlenmiş sürüm. Bu örnekte Ctrl+ alırken CPU profil oluşturucusunu durduruyorum 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
Goroutinin sinyali işlemek için işlemci süresine sahip olması için, ana goroutinin bir engelleme işlemini çağırması veya runtime.Goscheduygun bir yerde (varsa programınızın ana döngüsünde)
çağırması gerektiğini unutmayın


0

Ölüm , kanalları kullanan basit bir kütüphane ve kapatma sinyallerini beklemek için bir bekleme grubu. Sinyal alındıktan sonra, temizlemek istediğiniz tüm yapılarınızda bir close yöntemi çağıracaktır.


3
Pek çok kod satırı ve harici bir kütüphane bağımlılığı dört kod satırında ne yapılabilir? (kabul edilen cevaba göre)
Jacob

standart kapatma arayüzüne sahip olmaları durumunda, hepsini temizleme ve otomatik olarak kapatma yapılarında temizlemenizi sağlar.
Ben Aldrich

0

Sen syscall.SIGINT ve syscall.SIGTERM sinyallerini algılayan bir farklı goroutine varsa ve kullanarak, kanala geçirebilir signal.Notify . Bir kanal kullanarak o gorote bir kanca gönderebilir ve bir işlev dilimine kaydedebilirsiniz. Kanalda kapatma sinyali algılandığında, bu işlevleri dilimde yürütebilirsiniz. Bu, kaynakları temizlemek, goroutinleri çalıştırmak için bitirmek, veriyi sürdürmek veya kısmi çalıştırma toplamlarını yazdırmak için bekleyin.

Kapanışta kanca eklemek ve çalıştırmak için küçük ve basit bir yardımcı program yazdım. Umarım yardımcı olabilir.

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

Bunu 'erteleme' tarzında yapabilirsiniz.

bir sunucuyu incelikle kapatma örneği:

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

Bu, temizlemek için bazı görevleriniz olması durumunda çalışan başka bir sürümdür. Kod, yöntemlerinde temizleme işlemini bırakacaktır.

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
}

ana işlevi temizlemeniz gerekirse, go func () öğesini kullanarak ana iş parçacığındaki sinyali yakalamanız gerekir.


-2

Birisinin Windows'ta sinyalleri işlemek için bir yola ihtiyacı varsa kayıt için. Ben prog B çağırmak prog B os / exec üzerinden işlemek için bir gereksinim vardı ama prog B hiçbir zaman incelikle sonlandırmak mümkün ex üzerinden sinyal gönderme çünkü. cmd.Process.Signal (syscall.SIGTERM) veya diğer sinyaller Windows'ta desteklenmez. İşlediğim yol, eski bir sinyal olarak geçici bir dosya oluşturmaktır. .signal.term prog A ve prog B ile bu dosyanın aralık bazında olup olmadığını kontrol etmesi gerekir, eğer dosya varsa programdan çıkacak ve gerekirse bir temizleme işleyecektir, eminim başka yollar var ama bu iş yaptı.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.