本文探讨的问题:外部的kill信号能被多线程程序中的多个线程同时接收到吗?
结论
可以,这里用 golang 进行举例,多个goroutine通过 signal.Notify 注册消息的接收,然后在每个 goroutine 中都可以捕捉到kill的信号
实验样例
package main
import (
"fmt"
"os"
"os/signal"
"sync"
"syscall"
)
var wg = &sync.WaitGroup{}
func main() {
wg.Add(2)
go func() {
c1 := make(chan os.Signal, 1)
signal.Notify(c1, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
fmt.Printf("goroutine 1 receive a signal : %v\n\n", <-c1)
wg.Done()
}()
go func() {
c2 := make(chan os.Signal, 1)
signal.Notify(c2, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
fmt.Printf("goroutine 2 receive a signal : %v\n\n", <-c2)
wg.Done()
}()
wg.Wait()
fmt.Printf("all groutine done!\n")
}
运行结果如下:
样例解释
样例中使用两个goroutine分别注册了3个信号的监听,然后程序运行之后,使用 Ctrl + C 终止程序运行,看到输出结果,两个groutine都捕捉到信号,并完成了退出
为什么要做这个实验
生产环境中,我们可能用到的别人的SDK,在别人SDK中如果实现了类似于优雅退出的操作或者其他一些要使用到signal.Notify这种注册监听的用法,我经常怀疑,这个kill信号最终是谁接收到的,这里发现所有注册的就能接收到这个信号,可以称之为广播信号。
验证接收信号的顺序
在上面得到的结论是都能捕捉到kill信号,继续验证谁先捕捉到。
样例
package main
import (
"fmt"
"os"
"os/signal"
"sync"
"syscall"
)
var wg = &sync.WaitGroup{}
func main() {
//用来保证注册的先后顺序
order := make(chan int)
wg.Add(2)
go func() {
c1 := make(chan os.Signal, 1)
signal.Notify(c1, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
order <- 1
fmt.Printf("goroutine 1 receive a signal : %v\n\n", <-c1)
wg.Done()
}()
go func() {
//等到goroutine1注册完后再注册goroutine2
<-order
c2 := make(chan os.Signal, 1)
signal.Notify(c2, syscall.SIGINT, syscall.SIGTERM, syscall.SIGQUIT)
fmt.Printf("goroutine 2 receive a signal : %v\n\n", <-c2)
wg.Done()
}()
wg.Wait()
fmt.Printf("all groutine done!\n")
}
相同的代码,多次运行结果如下:
得出结论
捕捉信号的顺序和信号注册的顺序无关!
扩展
这里注册的三个信号的对应值。