เป็นไปได้หรือไม่ที่จะจับสัญญาณ Ctrl + C และเรียกใช้ฟังก์ชั่นการล้างข้อมูลในแบบ "เลื่อน"


207

ฉันต้องการจับสัญญาณCtrl+C( SIGINT) ที่ส่งจากคอนโซลและพิมพ์ผลรวมการวิ่งบางส่วน

เป็นไปได้ไหมที่ Golang?

หมายเหตุ: เมื่อครั้งแรกที่ผมโพสต์คำถามที่ผมสับสนเกี่ยวกับCtrl+Cการเป็นแทนSIGTERMSIGINT

คำตอบ:


260

คุณสามารถใช้แพคเกจระบบปฏิบัติการ / สัญญาณเพื่อจัดการสัญญาณที่เข้ามา 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
    }
}()

ลักษณะที่ทำให้โปรแกรมของคุณสิ้นสุดและพิมพ์ข้อมูลนั้นขึ้นอยู่กับคุณ


ขอบคุณ! ดังนั้น ... ^ C ไม่ใช่ SIGTERM ใช่ไหม อัพเดท: ขออภัยลิงค์ที่คุณให้นั้นมีรายละเอียดเพียงพอ!
Sebastián Grignoli

19
แทนที่จะfor sig := range g {คุณยังสามารถใช้<-sigchanในขณะที่คำตอบก่อนหน้านี้: stackoverflow.com/questions/8403862/...
เด Seguret

3
@dystroy: แน่นอนถ้าคุณจะยกเลิกโปรแกรมตามสัญญาณแรก โดยการใช้ลูปคุณสามารถรับสัญญาณทั้งหมดหากคุณตัดสินใจว่าจะไม่ยุติโปรแกรม
Lily Ballard

79
หมายเหตุ: คุณต้องสร้างโปรแกรมเพื่อให้สามารถใช้งานได้จริง หากคุณรันโปรแกรมผ่านทางgo runคอนโซลและส่ง SIGTERM ผ่าน ^ C สัญญาณจะถูกเขียนลงในช่องและโปรแกรมตอบสนอง แต่ดูเหมือนจะหลุดออกจากลูปโดยไม่คาดคิด นี่เป็นเพราะ SIGRERM ไปได้go runเช่นกัน! (นี่ทำให้ฉันสับสนมาก!)
วิลเลียม Pursell

5
โปรดทราบว่าเพื่อให้ goroutine รับเวลาประมวลผลเพื่อจัดการสัญญาณ goroutine หลักจะต้องเรียกการบล็อกหรือการโทรruntime.Goschedในที่ที่เหมาะสม (ในลูปหลักของโปรแกรมถ้ามี)
misterbee

107

งานนี้:

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
สำหรับผู้อ่านคนอื่น: ดูคำตอบของ @adutyuty สำหรับคำอธิบายว่าทำไมคุณถึงต้องการจับ os.Interrupt และ syscall.SIGTERM คงจะดีถ้าคุณรวมคำอธิบายไว้ในคำตอบนี้โดยเฉพาะเมื่อเขาโพสต์เดือนก่อนหน้าคุณ
Chris

1
ทำไมคุณถึงใช้แชนเนลที่ไม่ปิดกั้น จำเป็นหรือไม่
Awn

4
@Barry เหตุใดจึงมีขนาดบัฟเฟอร์ 2 แทน 1
pdeva

2
นี่คือข้อความที่ตัดตอนมาจากเอกสาร "สัญญาณแพ็คเกจจะไม่บล็อกการส่งไปยัง c: ผู้เรียกต้องตรวจสอบให้แน่ใจว่า c มีพื้นที่บัฟเฟอร์เพียงพอที่จะติดตามอัตราสัญญาณที่คาดไว้สำหรับช่องสัญญาณที่ใช้สำหรับการแจ้งเตือนเพียงค่าสัญญาณหนึ่งบัฟเฟอร์ขนาด 1 ก็เพียงพอแล้ว"
bmdelacruz

25

หากต้องการเพิ่มคำตอบอื่น ๆ เล็กน้อยหากคุณต้องการจับ SIGTERM (สัญญาณเริ่มต้นที่ส่งโดยคำสั่ง kill) คุณสามารถใช้syscall.SIGTERMแทน os.Interrupt ระวังว่าอินเตอร์เฟส syscall นั้นเป็นระบบเฉพาะและอาจไม่สามารถใช้งานได้ทุกที่ (เช่นบน windows) แต่มันใช้งานได้ดีในการจับทั้งคู่:

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

7
signal.Notifyฟังก์ชั่นช่วยให้การระบุสัญญาณหลายครั้ง signal.Notify(c, os.Interrupt, syscall.SIGTERM)ดังนั้นคุณสามารถลดความซับซ้อนของรหัสของคุณ
jochen

ฉันคิดว่าฉันพบว่าหลังจากโพสต์ แก้ไขแล้ว!
adamlamar

2
แล้ว os.Kill ล่ะ
Awn

2
@Eclipse คำถามที่ยอดเยี่ยม! os.Kill สอดคล้องกับsyscall.Killซึ่งเป็นสัญญาณที่สามารถส่งได้ แต่ไม่สามารถจับได้ kill -9 <pid>เทียบเท่าคำสั่ง หากคุณต้องการที่จะจับและปิดได้อย่างสง่างามที่คุณต้องใช้kill <pid> syscall.SIGTERM
adamlamar

@adamlamar Ahh นั่นสมเหตุสมผล ขอบคุณ!
Awn

18

มี (ในขณะที่โพสต์) หนึ่งหรือสองพิมพ์เล็ก ๆ น้อย ๆ ในคำตอบที่ได้รับการยอมรับข้างต้นดังนั้นนี่คือรุ่นที่ล้างแล้ว ในตัวอย่างนี้ผมหยุดสร้างโปรไฟล์ของ 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)                                                     
  }                                                                
}()    

2
โปรดทราบว่าเพื่อให้ goroutine รับเวลาประมวลผลเพื่อจัดการสัญญาณ goroutine หลักจะต้องเรียกการบล็อกหรือการโทรruntime.Goschedในที่ที่เหมาะสม (ในลูปหลักของโปรแกรมถ้ามี)
misterbee

8

ทุกอย่างที่กล่าวมาข้างต้นดูเหมือนจะทำงานได้เมื่อ spliced ​​แต่หน้าสัญญาณของ gobyexampleมีตัวอย่างที่ชัดเจนและสมบูรณ์ของการจับสัญญาณ มีมูลค่าเพิ่มในรายการนี้


0

ความตายเป็นห้องสมุดง่าย ๆ ที่ใช้แชนเนลและกลุ่มรอเพื่อรอสัญญาณการปิด เมื่อได้รับสัญญาณแล้วจะเรียกวิธีปิดบนโครงสร้างทั้งหมดของคุณที่คุณต้องการล้างข้อมูล


3
มีโค้ดหลายบรรทัดและพึ่งพาไลบรารีภายนอกเพื่อทำสิ่งที่สามารถทำได้ในสี่บรรทัดของโค้ด? (ตามคำตอบที่ยอมรับ)
Jacob

ช่วยให้คุณสามารถล้างข้อมูลทั้งหมดในแบบคู่ขนานและปิดโครงสร้างโดยอัตโนมัติหากมีส่วนต่อประสานมาตรฐานแบบปิด
Ben Aldrich

0

คุณสามารถมี 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()

0

นี่เป็นเวอร์ชันอื่นที่ใช้งานได้ในกรณีที่คุณมีงานที่ต้องล้างข้อมูล รหัสจะทำให้กระบวนการทำความสะอาดในวิธีการของพวกเขา

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 () เช่นกัน


-2

สำหรับบันทึกถ้ามีคนต้องการวิธีจัดการสัญญาณบน Windows ฉันมีความต้องการที่จะจัดการกับ prog A การเรียก prog B ถึง os / exec แต่ prog B ไม่สามารถยุติได้อย่างงดงามเนื่องจากการส่งสัญญาณผ่าน ex cmd.Process.Signal (syscall.SIGTERM) หรือสัญญาณอื่น ๆ ไม่ได้รับการรองรับบน Windows วิธีที่ฉันจัดการคือสร้างไฟล์ temp เป็นสัญญาณ .signal.term ถึง prog A และ prog B จำเป็นต้องตรวจสอบว่าไฟล์นั้นมีอยู่ในช่วงฐานหรือไม่ถ้าไฟล์มีอยู่มันจะออกจากโปรแกรมและจัดการกับการล้างข้อมูลถ้าจำเป็นฉันแน่ใจว่ามีวิธีอื่น ๆ

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.