如何在使用线程和队列时处理异常?

10 投票
1 回答
5846 浏览
提问于 2025-04-15 11:23

如果我有一个使用了线程和队列的程序,我该如何让异常停止程序的执行呢?下面是一个示例程序,这个程序无法通过按 ctrl-c 来停止(基本上是从 Python 文档中复制过来的)。

from threading import Thread
from Queue import Queue
from time import sleep

def do_work(item):
    sleep(0.5)
    print "working" , item

def worker():
        while True:
            item = q.get()
            do_work(item)
            q.task_done()

q = Queue()

num_worker_threads = 10

for i in range(num_worker_threads):
     t = Thread(target=worker)
    # t.setDaemon(True)
     t.start()

for item in range(1, 10000):
    q.put(item)

q.join()       # block until all tasks are done

1 个回答

6

最简单的方法是把所有的工作线程都设置为守护线程,然后你的主循环就可以是这样的:

while True:
    sleep(1)

当你按下 Ctrl+C 时,会在主线程中抛出一个异常,而所有的守护线程会在解释器退出时自动结束。这种方法假设你不需要在这些线程退出之前进行任何清理工作。

一种更复杂的方法是使用一个全局的 stopped 事件

stopped = Event()
def worker():
    while not stopped.is_set():
        try:
            item = q.get_nowait()
            do_work(item)
        except Empty:      # import the Empty exception from the Queue module
            stopped.wait(1)

这样,当你的主循环收到 KeyboardInterrupt(也就是 Ctrl+C)时,可以把 stopped 事件设置为 False

try:
    while not stopped.is_set():
        stopped.wait(1)
except KeyboardInterrupt:
    stopped.set()

这样做可以让你的工作线程完成它们正在做的事情,而不是让每个工作线程作为守护线程在执行过程中就直接退出。你还可以进行任何你想要的清理工作。

需要注意的是,这个例子没有使用 q.join(),虽然这样会让事情变得更复杂,但你仍然可以使用它。如果你使用了 q.join(),那么最好用信号处理器来检测 KeyboardInterrupt,而不是用异常。比如:

from signal import signal, SIGINT
def stop(signum, frame):
    stopped.set()
signal(SIGINT, stop)

这样你就可以定义按下 Ctrl+C 时会发生什么,而不会影响到主循环正在进行的操作。所以你可以继续使用 q.join(),而不用担心被 Ctrl+C 中断。当然,在我上面的例子中,你并不需要使用 join,但你可能有其他原因需要这样做。

撰写回答