是否可以以"延迟"的方式捕获Ctrl + C信号并运行清理功能?

Seb*_*oli 191 signals go sigterm

我想捕获从控制台发送的Ctrl+C(SIGINT)信号并打印出一些部分运行总计.

这在Golang有可能吗?

注意:当我第一次发布问题时,我很困惑Ctrl+CSIGTERM不是SIGINT.

Lil*_*ard 241

您可以使用os/signal包来处理传入的信号.^ C是SIGINT,因此您可以使用它来捕获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
    }
}()
Run Code Online (Sandbox Code Playgroud)

您导致程序终止和打印信息的方式完全取决于您.

  • 注意:您必须实际构建程序才能使其正常工作.如果你通过控制台中的`go run`运行程序并通过^ C发送SIGTERM,信号将被写入通道并且程序响应,但看起来意外地退出循环.这是因为SIGRERM也会"去运行"!(这引起了我很大的困惑!) (75认同)
  • 而不是`for sig:= range g {`,你也可以像之前的答案一样使用`<-sigchan`:http://stackoverflow.com/questions/8403862/do-actions-on-end-of-execution (18认同)
  • 请注意,为了让goroutine获得处理器时间来处理信号,主goroutine必须调用阻塞操作或在适当的位置调用`runtime.Gosched`(在程序的主循环中,如果有的话) (5认同)
  • @dystroy:当然,如果你真的要终止程序以响应第一个信号.通过使用循环,您可以捕获所有信号,如果您碰巧决定*不*终止程序. (3认同)

小智 97

这有效:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @Barry为什么缓冲区大小为2而不是1? (4认同)
  • 这是[文档](https://golang.org/pkg/os/signal/#Notify) 的摘录。“包信号不会阻止发送到 c:调用者必须确保 c 有足够的缓冲区空间来跟上预期的信号速率。对于仅用于通知一个信号值的通道,大小为 1 的缓冲区就足够了。” (2认同)

ada*_*mar 25

要稍微添加其他答案,如果您确实想要捕获SIGTERM(kill命令发送的默认信号),则可以使用syscall.SIGTERMos.Interrupt来代替.请注意,系统调用接口是特定于系统的,并且可能无法在任何地方工作(例如在Windows上).但它可以很好地捕捉到两者:

c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
....
Run Code Online (Sandbox Code Playgroud)

  • `signal.Notify`功能允许一次指定几个信号.因此,您可以将代码简化为`signal.Notify(c,os.Interrupt,syscall.SIGTERM)`. (7认同)
  • os.Kill 呢? (2认同)
  • @Eclipse很棒的问题!`os.Kill`​​ [对应](https://golang.org/pkg/os/#Signal)到`syscall.Kill`​​,这是一个可以发送但未被捕获的信号.它相当于命令`kill -9 <pid>`.如果你想捕获`kill <pid>`并正常关闭,你必须使用`syscall.SIGTERM`. (2认同)

gra*_*ron 17

在上面接受的答案中有(在发布时)一两个小错字,所以这里是清理版本.在这个例子中,我在接收Ctrl + C时停止CPU分析器.

// 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)                                                     
  }                                                                
}()    
Run Code Online (Sandbox Code Playgroud)

  • 请注意,为了让goroutine获得处理器时间来处理信号,主goroutine必须调用阻塞操作或在适当的位置调用`runtime.Gosched`(在程序的主循环中,如果有的话) (2认同)

wil*_*-ob 7

拼接后,所有上述内容似乎都能正常工作,但gobyexample的信号页面有一个非常干净完整的信号捕获示例.值得添加到此列表中.

  • @MikeGraf 来自 [`Notify` 示例代码](https://pkg.go.dev/os/signal#example-Notify):“我们必须使用缓冲通道,否则如果我们还没有准备好,就有丢失信号的风险当信号发送时接收。” (3认同)