我希望捕获从控制台发送的Ctrl+C (SIGINT
) 信号,并打印出一些部分运行总数。
这个有效:
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
}
}
请在操场结帐
os.Exit(1)
改为 os.Exit(0)
,然后运行 echo $?
,退出码是 1 而不是 0。 - jimouris补充其他答案的观点,如果你确实想捕获SIGTERM(kill命令发送的默认信号),你可以使用syscall.SIGTERM
代替os.Interrupt。请注意,syscall接口是特定于系统的,可能不适用于所有地方(例如在Windows上)。但它可以很好地捕获两者:
c := make(chan os.Signal, 2)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
....
os.Kill
对应 syscall.Kill
,它是一种可以发送但无法捕获的信号。它相当于命令kill -9 <pid>
。如果你想捕获 kill <pid>
并优雅地关闭进程,你需要使用 syscall.SIGTERM
。 - adamlamarsignal包不会阻塞对c的发送:调用者必须确保c具有足够的缓冲区空间以跟上预期的信号速率。
一个无缓冲通道可能根本捕获不到任何信号。通过为每个注册信号估计一个插槽,可以捕获大多数信号,但不能保证。 - adamlamar在发布时,接受的答案中存在一两个小错别字,因此这里是经过整理的版本。在这个例子中,我将在收到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)
}
}()
runtime.Gosched
(如果程序有主循环,则在其中调用)来让出 CPU 时间。 - misterbeego build
编译并在本地执行以下程序:https://go.dev/play/p/uVda7C4bZbe。我认为Go运行时会公平地分配每个活动的goroutine处理器时间来运行,并且示例程序似乎证实了这一点。您能分享一下您评论的基础是什么吗? - nishanthshanmugham以上所有方式似乎在拼接时都可以工作,但是gobyexample的信号页面有一个非常干净和完整的信号捕获示例。值得加入到这个列表中。
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func main() {
sigs := make(chan os.Signal, 1)
done := make(chan bool, 1)
signal.Notify(sigs, syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-sigs
fmt.Println()
fmt.Println(sig)
done <- true
}()
fmt.Println("awaiting signal")
<-done
fmt.Println("exiting")
}
来源:gobyexample.com/signals
当我们运行这个程序时,它会阻塞等待一个信号。通过键入Ctrl+C(终端显示为^C),我们可以发送SIGINT信号,导致程序打印interrupt然后退出。
signal.Notify函数注册给定的channel以接收指定信号的通知。
package main
import (
"fmt"
"os"
"os/signal"
"syscall"
)
func main() {
sig := make(chan os.Signal, 1)
done := make(chan bool, 1)
signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
go func() {
sig := <-sig
fmt.Println()
fmt.Println(sig)
done <- true
fmt.Println("ctrl+c")
}()
fmt.Println("awaiting signal")
<-done
fmt.Println("exiting")
}
检测HTTP请求取消
package main
import (
"fmt"
"net/http"
"time"
)
func main() {
mux := http.NewServeMux()
mux.HandleFunc("/path", func(writer http.ResponseWriter, request *http.Request) {
time.Sleep(time.Second * 5)
select {
case <-time.After(time.Millisecond * 10):
fmt.Println("started")
return
case <-request.Context().Done():
fmt.Println("canceled")
}
})
http.ListenAndServe(":8000", mux)
}
你可以创建一个不同的goroutine来检测syscall.SIGINT和syscall.SIGTERM信号,并使用signal.Notify将它们中继到一个通道中。你可以使用一个通道向那个goroutine发送一个钩子,并将其保存在函数切片中。当通道上检测到关闭信号时,你可以执行切片中的这些函数。这可以用于清理资源、等待正在运行的goroutine完成、持久化数据或打印部分运行总数。
我编写了一个小而简单的实用程序,在关闭时添加和运行钩子。希望它能有所帮助。
https://github.com/ankit-arora/go-utils/blob/master/go-shutdown-hook/shutdown-hook.go
你可以使用“defer”来实现这一点。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()在主线程中捕获信号。
ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
defer stop()
Death是一个简单的库,它使用通道和wait group来等待关闭信号。一旦接收到信号,它将调用您想要清理的所有结构体的close方法。
for sig := range g {
外,你还可以使用<-sigchan
,就像这个先前的答案中所示:https://dev59.com/MGoy5IYBdhLWcg3wq_wk - Denys Séguretruntime.Gosched
。 - misterbee