如何在使用线程和队列时处理异常?
如果我有一个使用了线程和队列的程序,我该如何让异常停止程序的执行呢?下面是一个示例程序,这个程序无法通过按 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
,但你可能有其他原因需要这样做。