终止多线程python程序


73

如何对Ctrl + C键事件做出多线程python程序响应?

编辑:代码是这样的:

import threading
current = 0

class MyThread(threading.Thread):
    def __init__(self, total):
        threading.Thread.__init__(self)
        self.total = total

    def stop(self):
        self._Thread__stop()

    def run(self):
        global current
        while current<self.total:
            lock = threading.Lock()
            lock.acquire()
            current+=1
            lock.release()
            print current

if __name__=='__main__':

    threads = []
    thread_count = 10
    total = 10000
    for i in range(0, thread_count):
        t = MyThread(total)
        t.setDaemon(True)
        threads.append(t)
    for i in range(0, thread_count):
        threads[i].start()

我试图在所有线程上删除join(),但仍然无法正常工作。是否因为每个线程的run()过程中的锁段?

编辑:上面的代码应该可以工作,但是当当前变量在5,000-6,000范围内并遍历以下错误时,它总是会中断

Exception in thread Thread-4 (most likely raised during interpreter shutdown):
Traceback (most recent call last):
  File "/usr/lib/python2.5/threading.py", line 486, in __bootstrap_inner
  File "test.py", line 20, in run
<type 'exceptions.TypeError'>: unsupported operand type(s) for +=: 'NoneType' and 'int'
Exception in thread Thread-2 (most likely raised during interpreter shutdown):
Traceback (most recent call last):
  File "/usr/lib/python2.5/threading.py", line 486, in __bootstrap_inner
  File "test.py", line 22, in run

FWIW,我也遇到了同样的问题,但是使用了更新的concurrent.futures模块。仍在尝试找出此处的解决方案是否或如何从转换threadingconcurrent.futures
Nick Chammas

Answers:


93

在启动主线程之前,将除主线程之外的每个线程都设为守护进程(t.daemon = True在2.6或更高版本中,t.setDaemon(True)在2.6或更低版本中,针对每个线程对象t)。这样,当主线程接收到KeyboardInterrupt时,如果它没有捕获到或捕获到它,但是无论如何决定终止,则整个过程将终止。请参阅文档

编辑:刚刚看过OP的代码(最初未发布)和声称“它不起作用”,看来我必须添加...:

当然,如果你希望你的主线程保持响应(例如,为了控制-C),不泥潭成阻塞调用,如join荷兰国际集团另一个线程-尤其是没有完全无用的阻塞调用,如join荷兰国际集团守护线程。例如,仅更改当前线程在主线程中的最终循环(完全无损):

for i in range(0, thread_count):
    threads[i].join()

像这样更明智:

while threading.active_count() > 0:
    time.sleep(0.1)

如果您的main没有比将所有线程自己终止或接收control-C(或其他信号)更好的事情了。

当然,如果您不想让线程突然终止(如守护线程可能),还有许多其他可用的模式-除非它们也被永久地陷入无条件阻塞的调用,死锁等;-) 。


嗯,您正在进行(无用的)阻塞调用-因此,对control-C毫无响应。解决方案非常简单:如果您想保持响应状态,请不要进行无用的阻塞调用。让我编辑我的答案来解释。
Alex Martelli,2009年

2
@jack,您现在提供的代码立即终止(当然,因为主线程“掉头了”,并且所有内容都结束了)。因此,它与您实际尝试的完全不同,很显然您甚至没有尝试过要发布的代码。真的很难帮助您!请发布您尝试过的代码,并确实再现您遇到的问题,因此,我可以向您展示您的错误在该代码中的位置,而不是试图猜测您看不见的代码可能存在的错误。
Alex Martelli,2009年

2
正如我已经提到的,@ jack,如果这是您的代码,那么您的主要错误现在是“主线程“掉头了”,并且一切都结束了”。尝试上面已经给出的代码:while threading.active_count() > 0: time.sleep(0.1)-为什么让我重复一遍?您可以做得更好(while线程中应该and有一个全局标志,以便可以干净地将其停止),但是您首先需要修复pther可怕的错误:它们在新本地变量的锁上包含了acquire / release,这与没有锁定,而不是由所有线程共享的锁定。
Alex Martelli,2009年

1
@AlexMartelli关于问题time.sleep()vs time.join(),请掠夺我的答案。join超时值有多大无关紧要,重要的是指定一个,这样它就可以保持响应。在我看来,使用具有如此短响应时间的睡眠计时器似乎是解决问题的一种过于棘手的解决方法。
Konrad Reiche 2012年

1
@AlexMartelli此代码具有无限循环,因为主线程保持活动状态。这意味着threading.active_count()将始终返回至少1
tvervack

16

有两种主要方法,一种是干净的,另一种是简单的。

干净的方法是在主线程中捕获KeyboardInterrupt,并设置一个标志,供后台线程检查以使其退出。这是一个使用全局的简单/稍微混乱的版本:

exitapp = False
if __name__ == '__main__':
    try:
        main()
    except KeyboardInterrupt:
        exitapp = True
        raise

def threadCode(...):
    while not exitapp:
        # do work here, watch for exitapp to be True

混乱但简单的方法是捕获KeyboardInterrupt并调用os._exit(),这将立即终止所有线程。


非常感谢,这非常困扰我:)
EmilStenström'09 -10-30

5

一个工人可能对你有所帮助:

#!/usr/bin/env python

import sys, time
from threading import *
from collections import deque

class Worker(object):
    def __init__(self, concurrent=1):
        self.concurrent = concurrent
        self.queue = deque([])
        self.threads = []
        self.keep_interrupt = False

    def _retain_threads(self):
        while len(self.threads) < self.concurrent:
            t = Thread(target=self._run, args=[self])
            t.setDaemon(True)
            t.start()
            self.threads.append(t)


    def _run(self, *args):
        while self.queue and not self.keep_interrupt:
            func, args, kargs = self.queue.popleft()
            func(*args, **kargs)

    def add_task(self, func, *args, **kargs):
        self.queue.append((func, args, kargs))

    def start(self, block=False):
        self._retain_threads()

        if block:
            try:
                while self.threads:
                    self.threads = [t.join(1) or t for t in self.threads if t.isAlive()]
                    if self.queue:
                        self._retain_threads()
            except KeyboardInterrupt:
                self.keep_interrupt = True
                print "alive threads: %d; outstanding tasks: %d" % (len(self.threads), len(self.queue))
                print "terminating..."


# example
print "starting..."
worker = Worker(concurrent=50)

def do_work():
    print "item %d done." % len(items)
    time.sleep(3)

def main():
    for i in xrange(1000):
        worker.add_task(do_work)
    worker.start(True)

main()
print "done."

# to keep shell alive
sys.stdin.readlines()

4

我宁愿使用此博客文章中建议的代码:

def main(args):

    threads = []
    for i in range(10):
        t = Worker()
        threads.append(t)
        t.start()

    while len(threads) > 0:
        try:
            # Join all threads using a timeout so it doesn't block
            # Filter out threads which have been joined or are None
            threads = [t.join(1000) for t in threads if t is not None and t.isAlive()]
        except KeyboardInterrupt:
            print "Ctrl-c received! Sending kill to threads..."
            for t in threads:
                t.kill_received = True

我已经改变是t.joint.join(1)t.join(1000) 。实际的秒数无关紧要,除非您指定超时数,否则主线程将保持对Ctrl + C的响应。KeyboardInterrupt上的except使信号处理更加明确。


3
该代码将在第一个循环后中断,因为t.join(1000)不会返回线程,而是None。因此,在第一个循环之后,您将有Nonein的列表threads
tmbo

这是等待线程加入而不等待的最佳技术,但仍然允许SIGINT
FujiApple


1

如果您像这样生成一个线程myThread = Thread(target = function),然后执行myThread.start(); myThread.join()。启动CTRL-C时,主线程不会退出,因为它正在等待该阻塞myThread.join()调用。要解决此问题,只需在.join()调用中添加超时。超时时间可以是您希望的时间。如果希望它无限期地等待,只需设置一个非常长的超时(例如99999)即可。这样做也是一种好习惯,myThread.daemon = True因此,当主线程(非守护程序)退出时,所有线程都退出。


1
thread1 = threading.Thread(target=your_procedure, args = (arg_1, arg_2))    
try:
      thread1.setDaemon(True)  # very important
      thread1.start()
except (KeyboardInterrupt, SystemExit):
      cleanup_stop_thread()
      sys.exit()

当您想杀死线程时,只需使用:

thread1.join(0)
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.