linux之在覆盖 go 的默认信号处理程序时如何避免竞争
tl;博士如果 go 运行时可以随时处理信号,我们如何安全地使用 signal.Ignore以一种在安装默认信号处理程序和我们在 main() 中的指令之间不是竞争的方式忽略 SIGINT运行
go docs for pkg/signal 说明了关于信号的默认行为
A SIGHUP, SIGINT, or SIGTERM signal causes the program to exit.
因此,编写一个旋转 CPU 的 golang 二进制文件,按 ctrl+c 发送 SIGINT,程序将退出。
现在,假设您要覆盖该行为。一种方法是忽略
signal.Ignore(syscall.SIGINT) 的信号。
但现在考虑以下
package main
import (
"fmt"
"os/signal"
"syscall"
"time"
)
func main() {
fmt.Println("Looping, SIGINT will have default behavior")
for start := time.Now(); time.Now().Before(start.Add(time.Second * 5)); {
}
signal.Ignore(syscall.SIGINT)
fmt.Println("OK")
for start := time.Now(); time.Now().Before(start.Add(time.Second * 5)); {
}
}
这里我们有一个简单的 golang 二进制文件,它循环了 5 秒。这是一个繁忙的循环,所以我们知道我们不会通过在 OS 线程上让出时间来参与协作多任务处理。然后它注册以忽略 SIGINT。
如果你尝试这个,你会注意到如果你在前 5 秒内输入 ctrl+c,程序将退出。这似乎是有道理的——我们得到了默认的 golang 运行时信号处理行为,因为我们还没有通过调用
signal.Ignore 来覆盖它。 .
现在也许我们可以通过移动
signal.Ignore 来解决这个问题。成为 main() 中的第一件事,但是该程序证明的是,go 运行时不保证默认信号处理程序不会在 main() 中的同步代码完成执行之前运行。
即使你移动它,我们似乎在竞争
我找不到这方面的文档。 go runtime 提供了哪些保证,让我确信信号不会在阶段 1 和阶段 2 之间到达?
请您参考如下方法:
TL;DR: 尽早捕获,例如,就在 main 的顶部.
正如评论所说,这似乎不是将其描述为问题的好方法,因为它在所有编程语言中都很通用:如果您没有为 syscall.SIGINT 设置信号处理程序,默认情况下你会在 SIGINT 上被杀死,一旦你有了,你就不会了。确实如此,但是任何程序都可以在启动期间被杀死,在它有机会开始捕获信号之前。无论编程语言如何,都是如此。它对 C 和 C++ 程序的影响与对 Go 程序的影响一样大。
那么,一般而言,您需要做的就是捕获或丢弃 SIGINT使用 signal.Notify(ch, os.Interrupt)很早,靠近你的 main 的顶部, 其中 ch是您为此创建的 channel 。1 然后您可以编写自己的无竞争代码以通过 goroutine 和 channel 处理此问题:
os.Exit (或者——可能更好——在 os.Reset 上使用 syscall.SIGINT,然后为自己提供 syscall.SIGINT 以生成正确的操作系统级退出状态,“被信号 1 杀死”2)。如果信号应该被忽略,只需删除 channel 通知。 有点相关:最近有一个针对
syscall.SIGPIPE 的修复程序。处理。特别是调用
signal.Ignore(syscall.SIGPIPE)应该忽略
SIGPIPE ,但没有。
This seems to be fixed in Go 1.14.
1正如软件包文档所指出的,故意捕获信号将绕过来自
nohup 的任何“预先忽略”状态。或
trap "" 1 2 15在外壳中。如果您想对此进行检查,请使用
signal.Ignored功能。
2如果
signal_unix.go导出
dieFromSignal函数,你也许可以直接使用它,但它没有。如果有一个与操作系统无关的包装器来至少干净地尝试这种自杀方式,那就太好了。这甚至可以使用
sigprocmask在操作系统级别使自杀尽可能无种族。
1.本站遵循行业规范,任何转载的稿件都会明确标注作者和来源;2.本站的原创文章,请转载时务必注明文章作者和来源,不尊重原创的行为我们将追究责任;3.作者投稿可能会经我们编辑修改或补充。



