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 SIGTERM
yerine kafam karıştı SIGINT
.
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 SIGTERM
yerine kafam karıştı SIGINT
.
Yanıtlar:
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.
go run
bir 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!)
runtime.Gosched
uygun bir yerde (varsa programınızın ana döngüsünde)
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
}
}
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.SIGTERM
os.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)
....
signal.Notify
Fonksiyon aynı anda birden sinyallerini belirlemenizi sağlar. Böylece, kodunuzu basitleştirebilirsiniz signal.Notify(c, os.Interrupt, syscall.SIGTERM)
.
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)
}
}()
runtime.Gosched
uygun bir yerde (varsa programınızın ana döngüsünde)
Yukarıdakilerin hepsi eklenmişken işe yarıyor gibi görünüyor, ancak gobyexample'nin sinyaller sayfasında sinyal yakalamanın gerçekten temiz ve eksiksiz bir örneği var. Bu listeye eklemeye değer.
Ö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.
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()
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.
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ı.