golang 系统信号处理

今天以前同事抛给我一个关于golang 信号捕获的问题。

这部分代码是用来捕获自定义信号:55, 并打印捕获的次数:

package main

import (
    "log"
    "os"
    "os/signal"
    "syscall"
    "time"
)

func main() {
    log.Println(os.Getpid())
    signalChans := make(chan os.Signal, 1024)

    signal.Notify(signalChans, syscall.Signal(55))
    i := 0
    for {
        select {
        case signalnumber := <-signalChans:
            i = i + 1
            log.Printf("receive signal %d %s %s", i, signalnumber.String(), time.Now().String())
        }
    }
}

下面是产生自定义信号的代码:

package main

import (
    "fmt"
    "sync"
    "syscall"
    "time"
)

func main() {

        i := 0
        wg := sync.WaitGroup{}
        for {
            i = i + 1
            wg.Add(1)
            go func() {
                err := syscall.Kill(2041194, 55)
                fmt.Printf("%s \n",err)
                wg.Done()
            }()
            if i > 100 {
                break
            }
        }
        wg.Wait()
        time.Sleep(10 * time.Second)
        fmt.Println("Done")
}

最终运行这两段代码会出现问题: 明明通过syscall.Kill发送了100次信号,但是捕获到的数量< 100, 数值不定。但是他说用c++能一致。 

我自己也运行了这两段代码,确实出现了这个问题。

接下来就看了go发送和接手信号的相关源码:

src/os/signal/signal_unix.go

//signal_unix.go
func loop() {
	for {
		process(syscall.Signal(signal_recv()))
	}
}

这个for循环用于获取信号,并将信号写入用户chan

runtime/sigqueue.go

文件中定义了  signal_recv() 和 sigsend(s uint32) 两个函数的具体实现。


// sig handles communication between the signal handler and os/signal.
// Other than the inuse and recv fields, the fields are accessed atomically.
//
// The wanted and ignored fields are only written by one goroutine at
// a time; access is controlled by the handlers Mutex in os/signal.
// The fields are only read by that one goroutine and by the signal handler.
// We access them atomically to minimize the race between setting them
// in the goroutine calling os/signal and the signal handler,
// which may be running in a different thread. That race is unavoidable,
// as there is no connection between handling a signal and receiving one,
// but atomic instructions should minimize it.

//此处各个数组的size就是3,   _NSIG(常量=65) ,可以算出信号值 <96(因为数组下表从0开始,最大信号为95)
//数组的值是uint32, 32位二进制, 这里用每一位代表一种信号的位置,共可以标识96种信号
var sig struct {
	note       note
	mask       [(_NSIG + 31) / 32]uint32
	wanted     [(_NSIG + 31) / 32]uint32
	ignored    [(_NSIG + 31) / 32]uint32
	recv       [(_NSIG + 31) / 32]uint32
	state      uint32
	delivering uint32
	inuse      bool
}

const (
	sigIdle = iota
	sigReceiving
	sigSending
	sigFixup
)

// sigsend delivers a signal from sighandler to the internal signal delivery queue.
// It reports whether the signal was sent. If not, the caller typically crashes the program.
// It runs from the signal handler, so it's limited in what it can do.
func sigsend(s uint32) bool {
	bit := uint32(1) << uint(s&31)
   //golang中信号最大值就是32*3,超过的就忽略
	if s >= uint32(32*len(sig.wanted)) {
		return false
	}

	atomic.Xadd(&sig.delivering, 1)
	// We are running in the signal handler; defer is not available.
    //判断信号是否被标记为需要捕获
	if w := atomic.Load(&sig.wanted[s/32]); w&bit == 0 {
		atomic.Xadd(&sig.delivering, -1)
		return false
	}

	// Add signal to outgoing queue.
    //尝试sig.mask中信号的标志位
	for {
		mask := sig.mask[s/32]
       
		if mask&bit != 0 {
            // //判断该信号是否已经标记为1了,已经标记的就不再重复设置
			atomic.Xadd(&sig.delivering, -1)
			return true // signal already in queue
		}

		if atomic.Cas(&sig.mask[s/32], mask, mask|bit) {
            //设置该信号的标志位为1
			break
		}
	}

	// Notify receiver that queue has new bit.
Send:
	for {
		switch atomic.Load(&sig.state) {
		default:
			throw("sigsend: inconsistent state")
		case sigIdle:
			if atomic.Cas(&sig.state, sigIdle, sigSending) {
				break Send
			}
		case sigSending:
			// notification already pending
			break Send
		case sigReceiving:
			if atomic.Cas(&sig.state, sigReceiving, sigIdle) {
				if GOOS == "darwin" || GOOS == "ios" {
					sigNoteWakeup(&sig.note)
					break Send
				}
				notewakeup(&sig.note)
				break Send
			}
		case sigFixup:
			// nothing to do - we need to wait for sigIdle.
			mDoFixupAndOSYield()
		}
	}

	atomic.Xadd(&sig.delivering, -1)
	return true
}

// sigRecvPrepareForFixup is used to temporarily wake up the
// signal_recv() running thread while it is blocked waiting for the
// arrival of a signal. If it causes the thread to wake up, the
// sig.state travels through this sequence: sigReceiving -> sigFixup
// -> sigIdle -> sigReceiving and resumes. (This is only called while
// GC is disabled.)
//go:nosplit
func sigRecvPrepareForFixup() {
	if atomic.Cas(&sig.state, sigReceiving, sigFixup) {
		notewakeup(&sig.note)
	}
}

// Called to receive the next queued signal.
// Must only be called from a single goroutine at a time.
//go:linkname signal_recv os/signal.signal_recv
func signal_recv() uint32 {
	for {
		// Serve any signals from local copy.
        //读取所有收到的信号, 找到第一个不为0的标志位,设置为0,并返回该信号值
		for i := uint32(0); i < _NSIG; i++ {
			if sig.recv[i/32]&(1<<(i&31)) != 0 {
				sig.recv[i/32] &^= 1 << (i & 31)
				return i
			}
		}

		// Wait for updates to be available from signal sender.
	Receive:
		for {
			switch atomic.Load(&sig.state) {
			default:
				throw("signal_recv: inconsistent state")
			case sigIdle:
				if atomic.Cas(&sig.state, sigIdle, sigReceiving) {
					if GOOS == "darwin" || GOOS == "ios" {
						sigNoteSleep(&sig.note)
						break Receive
					}
					notetsleepg(&sig.note, -1)
					noteclear(&sig.note)
					if !atomic.Cas(&sig.state, sigFixup, sigIdle) {
						break Receive
					}
					// Getting here, the code will
					// loop around again to sleep
					// in state sigReceiving. This
					// path is taken when
					// sigRecvPrepareForFixup()
					// has been called by another
					// thread.
				}
			case sigSending:
				if atomic.Cas(&sig.state, sigSending, sigIdle) {
					break Receive
				}
			}
		}

		// Incorporate updates from sender into local copy.
       //通过原子操作将标志位sig.maks拷贝到 sig.recv中,并重置mask中标志为0
		for i := range sig.mask {
			sig.recv[i] = atomic.Xchg(&sig.mask[i], 0)
		}
	}
}

所以如果我们通过两个协程或两个进程分别去产生信号,捕获信号,其实只是并发读写系统sig.mask中的标志位,而信号获取速度由go的调度决定,如果标记为1,但来消费端未来的及读取,生产端又触发了写,消费端自然就丢失了一次数据。

你可能感兴趣的:(知识点笔记,golang,开发语言,后端)