ichuan.net

自信打不死的心态活到老

Lock 和 Signal

在 unix 中,正在运行的程序收到 signal 后,会暂停在当前执行的代码处,转而执行注册的 signal handler,之后再返回正常执行流程。但如果程序收到信号时正在一个 lock 中呢?看下面的代码:

import signal
from threading import Condition

cond = Condition()

def func():
  print 'waiting...'
  with cond:
    cond.wait()
  print 'got notified'

def sig_handler(signum, frame):
  print 'notifing'
  with cond:
    cond.notify()

signal.signal(signal.SIGUSR1, sig_handler)
func()

首先给 SIGUSR1 信号注册了一个 handler,然后执行 func 时会输出个 waiting... 然后卡住。等我们执行 kill -SIGUSR1 <pid> 时,想象中应该会执行 sig_handler,解除 cond 的 lock,然后 func 函数中的 wait() 得以返回,输出 got notified

但实际测试发现,程序执行后,输出了 waiting...,然后无论发送什么信号都不会再有输出,只能 Ctrl+\ 结束进程。

这是为什么呢?查阅了 unix 的 wiki 后发现:

When a signal is sent, the operating system interrupts the target process's normal flow of execution to deliver the signal. Execution can be interrupted during any non-atomic instruction

原来只有程序出在非原子操作处时操作系统才能暂停程序的继续执行,转而执行 signal handler。而根据原子操作的描述,信号量、lock 等皆是原子操作。查阅了 python threading 模块代码发现,Condition.wait 内部实际调用的正是 lock:

enter image description here

这就解释了上面的例子为什么没能处理发送给它的信号了。

那如果既要用 Condition 又要用 signal 怎么办?通过查阅源码发现,Condition.wait 接受一个数字参数作为超时时间,内部是用 sleep 实现的,没有用 lock。而 unix 的 sleep 操作是在超时后或者收到信号后(参阅 man 3 sleep)返回。所以上例中代码改成如下:

import signal
from threading import Condition

cond = Condition()
got = False

def func():
  print 'waiting...'
  while not got:
    with cond:
      cond.wait(1)
  print 'got notified'

def sig_handler(signum, frame):
  global got
  print 'notifing'
  with cond:
    got = True
    cond.notify()
  print 'notified'

signal.signal(signal.SIGUSR1, sig_handler)
func()

使用了一个全局变量来记录是否成功收到了信号,然后 func 中每隔 1 秒会检测这个全局变量。这次执行后,发送 SIGUSR1 信号,程序依次输出 waiting...notifingnotifiedgot notified 后退出。和预期效果一致。

Comments