是否可以以“延迟”方式捕获Ctrl + C信号并运行清除功能?


Answers:


260

您可以使用os / signal包来处理输入信号。Ctrl+ CSIGINT,因此您可以使用它来捕获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
    }
}()

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


谢谢!那么... ^ C不是SIGTERM,然后呢?更新:对不起,您提供的链接已经足够详细了!
塞巴斯蒂安·格里戈诺利

19
相反的for sig := range g {,你也可以使用<-sigchan如在此以前的答案:stackoverflow.com/questions/8403862/...
丹尼斯·塞居勒

3
@dystroy:当然,如果您实际上是要响应第一个信号终止程序。通过循环使用,如果您决定终止程序,则可以捕获所有信号。
莉莉·巴拉德

79
注意:您必须实际构建程序才能使其正常工作。如果通过go run控制台运行程序并通过^ C发送SIGTERM,则信号将写入通道,并且程序会做出响应,但似乎意外退出循环。这是因为SIGRERM go run同样适用!(这使我感到困惑!)
William Pursell 2012年

5
请注意,为了使goroutine能够获得处理器时间来处理信号,主goroutine必须调用阻塞操作或runtime.Gosched在适当的位置调用(在程序的主循环中,如果有的话)
misterbee 2013年

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
对于其他读者:请查看@adamonduty的答案,以获取有关您为什么想要捕获os.Interrupt和syscall.SIGTERM的解释。将他的解释包括在此答案中将是一件很不错的事情,尤其是自从他在您发布几个月之前发布以来。
克里斯

1
为什么使用非阻塞通道?这有必要吗?
Awn

4
@Barry为什么缓冲区大小是2而不是1?
pdeva'5

2
这是文档摘录。“打包信号将不会阻止发送到c:调用者必须确保c有足够的缓冲区空间来跟上预期的信号速率。对于仅通知一个信号值的通道,大小为1的缓冲区就足够了。”
bmdelacruz

25

要稍微增加其他答案,如果您确实想捕获SIGTERM(kill命令发送的默认信号),则可以使用syscall.SIGTERMos.Interrupt代替。请注意,系统调用接口是特定于系统的,可能无法在任何地方(例如,在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 2013年

我想我在发布后发现了这一点。固定!
adamlamar

2
os.Kill呢?
Awn

2
@Eclipse很好的问题!os.Kill 对应syscall.Kill,这是一个可以发送但无法捕获的信号。它相当于命令kill -9 <pid>。如果要捕获kill <pid>并正常关闭,则必须使用syscall.SIGTERM
adamlamar

@adamlamar啊,有道理。谢谢!
Awn

18

上面接受的答案中有(在发布时)一两个小错字,所以这是清理后的版本。在此示例中,我在接收Ctrl+ 时停止CPU分析器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
请注意,为了使goroutine能够获得处理器时间来处理信号,主goroutine必须调用阻塞操作或runtime.Gosched在适当的位置调用(在程序的主循环中,如果有的话)
misterbee 2013年


0

死亡是一个简单的库,它使用通道和一个等待组来等待关闭信号。接收到信号后,它将在要清除的所有结构上调用close方法。


3
如此多的代码行和一个外部库依赖于执行四行代码中的操作?(根据公认的答案)
雅各布(Jacob

它允许您并行清理所有结构,并在结构具有标准关闭接口的情况下自动关闭结构。
本·奥尔德里奇

0

您可以使用其他goroutine来检测syscall.SIGINT和syscall.SIGTERM信号,并使用signal.Notify将它们中继到通道。您可以使用通道将钩子发送到该goroutine,并将其保存在功能片中。在通道上检测到关闭信号时,可以在切片中执行这些功能。这可用于清理资源,等待运行的goroutine完成,保留数据或打印部分运行总计。

我写了一个小而简单的实用程序来在关闭时添加和运行钩子。希望对您有所帮助。

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
}

如果您需要清理main函数,则还需要使用go func()捕获主线程中的信号。


-2

仅供记录,如果有人需要在Windows上处理信号的方法。我有一个要处理的问题,就是要从程序A通过os / exec调用程序B,但是程序B不能正常终止,因为要通过ex发送信号。Windows不支持cmd.Process.Signal(syscall.SIGTERM)或其他信号。我处理的方法是通过创建一个临时文件作为信号ex。通过程序A和程序B的.signal.term需要检查该文件是否存在于间隔基础上,如果文件存在,它将退出程序并在需要时进行清理,我敢肯定还有其他方法,但这可以完成工作。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.