我想捕获从控制台发送的Ctrl+C(SIGINT
)信号并打印出部分运行总计。
Golang有可能吗?
注意:当我第一次发布问题时,我对Ctrl+C被SIGTERM
代替感到困惑SIGINT
。
我想捕获从控制台发送的Ctrl+C(SIGINT
)信号并打印出部分运行总计。
Golang有可能吗?
注意:当我第一次发布问题时,我对Ctrl+C被SIGTERM
代替感到困惑SIGINT
。
Answers:
您可以使用os / signal包来处理输入信号。Ctrl+ 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
}
}()
导致程序终止和打印信息的方式完全取决于您。
go run
控制台运行程序并通过^ C发送SIGTERM,则信号将写入通道,并且程序会做出响应,但似乎意外退出循环。这是因为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代替。请注意,系统调用接口是特定于系统的,可能无法在任何地方(例如,在Windows上)工作。但这两种方法很好地结合在一起:
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
....
signal.Notify
功能允许一次指定多个信号。因此,您可以将代码简化为signal.Notify(c, os.Interrupt, syscall.SIGTERM)
。
上面接受的答案中有(在发布时)一两个小错字,所以这是清理后的版本。在此示例中,我在接收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)
}
}()
runtime.Gosched
在适当的位置调用(在程序的主循环中,如果有的话)
上面的所有这些在拼接时似乎都可以使用,但是gobyexample的信号页面有一个非常干净完整的信号捕获示例。值得添加到此列表中。
您可以使用其他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()
这是另一个版本,可以在您有一些任务需要清理时使用。代码将保留其方法中的清理过程。
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()捕获主线程中的信号。