异步KeyboardInterrupt和多线程

1 投票
2 回答
889 浏览
提问于 2025-04-17 06:35

看起来在多线程程序中,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 个回答

0

另外,你也可以使用 pause() 这个函数,它属于 signal 模块,来替代 reader_ready.wait()signal.pause() 是一个阻塞函数,也就是说它会一直停在那里,直到接收到某个信号才会继续执行。在你的情况中,当你按下 ^C 时,SIGINT 信号会让这个函数继续运行。

根据文档,这个函数在 Windows 系统上是不可用的。我在 Linux 上测试过,效果很好。我觉得这个方法比使用带有超时的 wait() 更好。

1

一个 threading._Event 对象的 wait() 方法其实是依赖于 thread.lockacquire() 方法来工作的。不过,线程文档提到,锁的 acquire() 方法是不能被打断的,也就是说,如果你按下了键盘中断(KeyboardInterrupt),这个异常会在锁被释放后才会处理。

所以,简单来说,这种行为是正常的。实现这种行为的线程对象在某个时刻都会依赖于锁(包括队列),所以你可能需要考虑其他的解决方案。

撰写回答