经验首页 前端设计 程序设计 Java相关 移动开发 数据库/运维 软件/图像 大数据/云计算 其他经验
当前位置:技术经验 » 程序设计 » Go语言 » 查看文章
关于 signal.Notify 的一个小问题
来源:cnblogs  作者:alfred_zhong  时间:2021/3/1 9:16:51  对本文有异议

前些天,给同事 review 一个 MR。MR 本身没什么问题,merge 完之后突发奇想跑了一下 golangci-lint 看看有没有啥问题。看到一个 issue 如下所示:

  1. main.go:102:16: SA1017: the channel used with signal.Notify should be buffered (staticcheck)
  2. signal.Notify(ch, syscall.SIGTERM, syscall.SIGQUIT, syscall.SIGINT)

很好奇,以前从来没见过这个 issue。于是查看了一下源码发现了问题。

虽然以前看网上的代码 signal.Notify 也注意到别人都有分配了带 buffer 的 channel,但是也没有细想。查看 signal.Notify 的源码,在 signal.go 中:

  1. // Notify causes package signal to relay incoming signals to c.
  2. // If no signals are provided, all incoming signals will be relayed to c.
  3. // Otherwise, just the provided signals will.
  4. //
  5. // Package signal will not block sending to c: the caller must ensure
  6. // that c has sufficient buffer space to keep up with the expected
  7. // signal rate. For a channel used for notification of just one signal value,
  8. // a buffer of size 1 is sufficient.
  9. //
  10. // It is allowed to call Notify multiple times with the same channel:
  11. // each call expands the set of signals sent to that channel.
  12. // The only way to remove signals from the set is to call Stop.
  13. //
  14. // It is allowed to call Notify multiple times with different channels
  15. // and the same signals: each channel receives copies of incoming
  16. // signals independently.
  17. func Notify(c chan<- os.Signal, sig ...os.Signal) {
  18. if c == nil {
  19. panic("os/signal: Notify using nil channel")
  20. }
  21. handlers.Lock()
  22. defer handlers.Unlock()
  23. h := handlers.m[c]
  24. if h == nil {
  25. if handlers.m == nil {
  26. handlers.m = make(map[chan<- os.Signal]*handler)
  27. }
  28. h = new(handler)
  29. handlers.m[c] = h
  30. }
  31. add := func(n int) {
  32. if n < 0 {
  33. return
  34. }
  35. if !h.want(n) {
  36. h.set(n)
  37. if handlers.ref[n] == 0 {
  38. enableSignal(n)
  39. // The runtime requires that we enable a
  40. // signal before starting the watcher.
  41. watchSignalLoopOnce.Do(func() {
  42. if watchSignalLoop != nil {
  43. go watchSignalLoop()
  44. }
  45. })
  46. }
  47. handlers.ref[n]++
  48. }
  49. }
  50. if len(sig) == 0 {
  51. for n := 0; n < numSig; n++ {
  52. add(n)
  53. }
  54. } else {
  55. for _, s := range sig {
  56. add(signum(s))
  57. }
  58. }
  59. }

注释中明确说明了需要传递带 buffer 的 channel。关注其中的 go watchSignalLoop(),在 signal_unix.go 中:

  1. func loop() {
  2. for {
  3. process(syscall.Signal(signal_recv()))
  4. }
  5. }
  6. func init() {
  7. watchSignalLoop = loop
  8. }

process(sig os.Signal) 函数定义又在 signal.go 中:

  1. func process(sig os.Signal) {
  2. n := signum(sig)
  3. if n < 0 {
  4. return
  5. }
  6. handlers.Lock()
  7. defer handlers.Unlock()
  8. for c, h := range handlers.m {
  9. if h.want(n) {
  10. // send but do not block for it
  11. select {
  12. case c <- sig:
  13. default:
  14. }
  15. }
  16. }
  17. // Avoid the race mentioned in Stop.
  18. for _, d := range handlers.stopping {
  19. if d.h.want(n) {
  20. select {
  21. case d.c <- sig:
  22. default:
  23. }
  24. }
  25. }
  26. }

注意中段的 select 代码块和注释,发现 sig 并不会阻塞发送给 c,如果 c 当前没有被 recv,则 sig 会被丢弃。这就造成了 sig 可能丢失的情况产生,也就是 golangci-lint 中提示的问题。


os.signal 的代码还是设计的相当精巧和高效的。

  1. var handlers struct {
  2. sync.Mutex
  3. // Map a channel to the signals that should be sent to it.
  4. m map[chan<- os.Signal]*handler
  5. // Map a signal to the number of channels receiving it.
  6. ref [numSig]int64
  7. // Map channels to signals while the channel is being stopped.
  8. // Not a map because entries live here only very briefly.
  9. // We need a separate container because we need m to correspond to ref
  10. // at all times, and we also need to keep track of the *handler
  11. // value for a channel being stopped. See the Stop function.
  12. stopping []stopping
  13. }

用一个 handlers 来存储关系。m 映射接收 channel 到相关 signal 的关系,ref 映射每一类 signal 有几个 channel 需要接收。其中 handler 结构体定义:

  1. type handler struct {
  2. mask [(numSig + 31) / 32]uint32
  3. }
  4. func (h *handler) want(sig int) bool {
  5. return (h.mask[sig/32]>>uint(sig&31))&1 != 0
  6. }
  7. func (h *handler) set(sig int) {
  8. h.mask[sig/32] |= 1 << uint(sig&31)
  9. }
  10. func (h *handler) clear(sig int) {
  11. h.mask[sig/32] &^= 1 << uint(sig&31)
  12. }

用三个长度的 uint32 来存储所有的 signal。每个 signal 占 1 个 bit 位。

还是不得不感叹大师级别的程序员写的东西,连一个字节都不舍得浪费。

原文链接:http://www.cnblogs.com/snowInPluto/p/14438948.html

 友情链接:直通硅谷  点职佳  北美留学生论坛

本站QQ群:前端 618073944 | Java 606181507 | Python 626812652 | C/C++ 612253063 | 微信 634508462 | 苹果 692586424 | C#/.net 182808419 | PHP 305140648 | 运维 608723728

W3xue 的所有内容仅供测试,对任何法律问题及风险不承担任何责任。通过使用本站内容随之而来的风险与本站无关。
关于我们  |  意见建议  |  捐助我们  |  报错有奖  |  广告合作、友情链接(目前9元/月)请联系QQ:27243702 沸活量
皖ICP备17017327号-2 皖公网安备34020702000426号