异步KeyboardInterrupt和多线程
看起来在多线程程序中,Python 对异步信号的处理不是很正确。不过,我想在这里看看是否有人能指出我哪里违反了某些原则,或者对某个概念理解错了。
我在这里找到了类似的讨论,但没有一个完全相同的。
场景是这样的:我有两个线程,一个是读取线程,另一个是写入线程(主线程)。写入线程向一个管道写数据,而读取线程则不断检查这个管道。两个线程之间通过一个 threading.Event()
来协调(我猜这个是用 pthread_cond_wait
实现的)。主线程在等待这个 Event
,而读取线程最终会设置这个事件。
但是,如果我想在主线程等待 Event
的时候中断我的程序,KeyboardInterrupt 并没有被异步处理。
这里有一个小程序来说明我的观点:
#!/usr/bin/python
import os
import sys
import select
import time
import threading
pfd_r = -1
pfd_w = -1
reader_ready = threading.Event()
class Reader(threading.Thread):
"""Read data from pipe and echo to stdout."""
def run(self):
global pfd_r
while True:
if select.select([pfd_r], [], [], 1)[0] == [pfd_r]:
output = os.read(pfd_r, 1000)
sys.stdout.write("R> '%s'\n" % output)
sys.stdout.flush()
# Suppose there is some long-running processing happening:
time.sleep(10)
reader_ready.set()
# Set up pipe.
(pfd_r, pfd_w) = os.pipe()
rt = Reader()
rt.daemon = True
rt.start()
while True:
reader_ready.clear()
user_input = raw_input("> ").strip()
written = os.write(pfd_w, user_input)
assert written == len(user_input)
# Wait for reply -- Try to ^C here and it won't work immediately.
reader_ready.wait()
用 './bug.py' 启动程序,然后在提示符下输入一些内容。当你看到读取线程以 'R>' 开头的回复时,试着用 ^C
来中断。
我在(Ubuntu Linux 10.10,Python 2.6.6)上看到的是,^C
直到阻塞的 reader_ready.wait()
返回后才被处理。我本来期待的是 ^C
能够异步触发,这样程序就会终止(因为我没有捕获 KeyboardInterrupt)。
这可能看起来是个牵强的例子,但我在一个实际的程序中遇到了这个问题,time.sleep(10)
被实际的计算替代了。
我是不是做错了什么明显的事情,比如误解了预期的结果?
补充:我刚刚在 Python 3.1.1 上测试了一下,问题依然存在。
2 个回答
另外,你也可以使用 pause()
这个函数,它属于 signal 模块,来替代 reader_ready.wait()
。signal.pause()
是一个阻塞函数,也就是说它会一直停在那里,直到接收到某个信号才会继续执行。在你的情况中,当你按下 ^C
时,SIGINT 信号会让这个函数继续运行。
根据文档,这个函数在 Windows 系统上是不可用的。我在 Linux 上测试过,效果很好。我觉得这个方法比使用带有超时的 wait()
更好。
一个 threading._Event
对象的 wait()
方法其实是依赖于 thread.lock
的 acquire()
方法来工作的。不过,线程文档提到,锁的 acquire()
方法是不能被打断的,也就是说,如果你按下了键盘中断(KeyboardInterrupt
),这个异常会在锁被释放后才会处理。
所以,简单来说,这种行为是正常的。实现这种行为的线程对象在某个时刻都会依赖于锁(包括队列),所以你可能需要考虑其他的解决方案。