ฉันต้องการจับสัญญาณCtrl+C( SIGINT
) ที่ส่งจากคอนโซลและพิมพ์ผลรวมการวิ่งบางส่วน
เป็นไปได้ไหมที่ Golang?
หมายเหตุ: เมื่อครั้งแรกที่ผมโพสต์คำถามที่ผมสับสนเกี่ยวกับCtrl+Cการเป็นแทนSIGTERM
SIGINT
ฉันต้องการจับสัญญาณCtrl+C( SIGINT
) ที่ส่งจากคอนโซลและพิมพ์ผลรวมการวิ่งบางส่วน
เป็นไปได้ไหมที่ Golang?
หมายเหตุ: เมื่อครั้งแรกที่ผมโพสต์คำถามที่ผมสับสนเกี่ยวกับCtrl+Cการเป็นแทนSIGTERM
SIGINT
คำตอบ:
คุณสามารถใช้แพคเกจระบบปฏิบัติการ / สัญญาณเพื่อจัดการสัญญาณที่เข้ามา Ctrl+ Cเป็นSIGINTos.Interrupt
เพื่อให้คุณสามารถใช้วิธีนี้กับดัก
c := make(chan os.Signal, 1)
signal.Notify(c, os.Interrupt)
go func(){
for sig := range c {
// sig is a ^C, handle it
}
}()
ลักษณะที่ทำให้โปรแกรมของคุณสิ้นสุดและพิมพ์ข้อมูลนั้นขึ้นอยู่กับคุณ
for sig := range g {
คุณยังสามารถใช้<-sigchan
ในขณะที่คำตอบก่อนหน้านี้: stackoverflow.com/questions/8403862/...
go run
คอนโซลและส่ง SIGTERM ผ่าน ^ C สัญญาณจะถูกเขียนลงในช่องและโปรแกรมตอบสนอง แต่ดูเหมือนจะหลุดออกจากลูปโดยไม่คาดคิด นี่เป็นเพราะ SIGRERM ไปได้go run
เช่นกัน! (นี่ทำให้ฉันสับสนมาก!)
runtime.Gosched
ในที่ที่เหมาะสม (ในลูปหลักของโปรแกรมถ้ามี)
งานนี้:
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
}
}
หากต้องการเพิ่มคำตอบอื่น ๆ เล็กน้อยหากคุณต้องการจับ SIGTERM (สัญญาณเริ่มต้นที่ส่งโดยคำสั่ง kill) คุณสามารถใช้syscall.SIGTERM
แทน os.Interrupt ระวังว่าอินเตอร์เฟส syscall นั้นเป็นระบบเฉพาะและอาจไม่สามารถใช้งานได้ทุกที่ (เช่นบน windows) แต่มันใช้งานได้ดีในการจับทั้งคู่:
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
....
signal.Notify
ฟังก์ชั่นช่วยให้การระบุสัญญาณหลายครั้ง signal.Notify(c, os.Interrupt, syscall.SIGTERM)
ดังนั้นคุณสามารถลดความซับซ้อนของรหัสของคุณ
มี (ในขณะที่โพสต์) หนึ่งหรือสองพิมพ์เล็ก ๆ น้อย ๆ ในคำตอบที่ได้รับการยอมรับข้างต้นดังนั้นนี่คือรุ่นที่ล้างแล้ว ในตัวอย่างนี้ผมหยุดสร้างโปรไฟล์ของ CPU เมื่อได้รับ+CtrlC
// 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
ในที่ที่เหมาะสม (ในลูปหลักของโปรแกรมถ้ามี)
ทุกอย่างที่กล่าวมาข้างต้นดูเหมือนจะทำงานได้เมื่อ spliced แต่หน้าสัญญาณของ gobyexampleมีตัวอย่างที่ชัดเจนและสมบูรณ์ของการจับสัญญาณ มีมูลค่าเพิ่มในรายการนี้
ความตายเป็นห้องสมุดง่าย ๆ ที่ใช้แชนเนลและกลุ่มรอเพื่อรอสัญญาณการปิด เมื่อได้รับสัญญาณแล้วจะเรียกวิธีปิดบนโครงสร้างทั้งหมดของคุณที่คุณต้องการล้างข้อมูล
คุณสามารถมี goroutine ที่แตกต่างกันที่ตรวจพบ syscall.SIGINT และ syscall.SIGTERM สัญญาณและถ่ายทอดไปยังช่องทางใช้signal.Notify signal.Notifyคุณสามารถส่งเบ็ดไปที่ goroutine นั้นโดยใช้ช่องทางและบันทึกไว้ในส่วนของฟังก์ชั่น เมื่อตรวจพบสัญญาณการปิดในช่องคุณสามารถดำเนินการฟังก์ชั่นเหล่านั้นในชิ้น สิ่งนี้สามารถใช้ในการทำความสะอาดทรัพยากรรอการเรียกใช้ goroutines เพื่อเสร็จสิ้นเก็บข้อมูลหรือพิมพ์ผลรวมการรันบางส่วน
ฉันเขียนโปรแกรมอรรถประโยชน์ขนาดเล็กและเรียบง่ายเพื่อเพิ่มและเรียกใช้ hooks เมื่อปิดเครื่อง หวังว่าจะสามารถช่วยได้
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()
นี่เป็นเวอร์ชันอื่นที่ใช้งานได้ในกรณีที่คุณมีงานที่ต้องล้างข้อมูล รหัสจะทำให้กระบวนการทำความสะอาดในวิธีการของพวกเขา
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 () เช่นกัน
สำหรับบันทึกถ้ามีคนต้องการวิธีจัดการสัญญาณบน Windows ฉันมีความต้องการที่จะจัดการกับ prog A การเรียก prog B ถึง os / exec แต่ prog B ไม่สามารถยุติได้อย่างงดงามเนื่องจากการส่งสัญญาณผ่าน ex cmd.Process.Signal (syscall.SIGTERM) หรือสัญญาณอื่น ๆ ไม่ได้รับการรองรับบน Windows วิธีที่ฉันจัดการคือสร้างไฟล์ temp เป็นสัญญาณ .signal.term ถึง prog A และ prog B จำเป็นต้องตรวจสอบว่าไฟล์นั้นมีอยู่ในช่วงฐานหรือไม่ถ้าไฟล์มีอยู่มันจะออกจากโปรแกรมและจัดการกับการล้างข้อมูลถ้าจำเป็นฉันแน่ใจว่ามีวิธีอื่น ๆ