Завершить многопоточную программу на Python - PullRequest
70 голосов
/ 28 октября 2009

Как сделать многопоточный ответ программы Python на событие нажатия клавиш Ctrl + C?

Редактировать: Код выглядит так:

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 () каждого потока?

Редактировать: Предполагается, что приведенный выше код работает, но он всегда прерывается, когда текущая переменная находится в диапазоне 5000-6000, и из-за ошибок, как показано ниже

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

Ответы [ 7 ]

89 голосов
/ 28 октября 2009

Сделать каждый поток, кроме основного, демоном (t.daemon = True в 2.6 или лучше, t.setDaemon(True) в 2.6 или меньше, для каждого объекта потока t перед его запуском). Таким образом, когда основной поток получает KeyboardInterrupt, если он не перехватывает его или перехватывает его, но все равно решает прекратить работу, весь процесс завершается. См. документы .

edit : только что увидев код OP (первоначально не опубликованный) и утверждение, что «это не работает», я должен добавить ...:

Конечно, если вы хотите, чтобы ваш основной поток оставался отзывчивым (например, на control-C), не включайте его в блокировку вызовов, таких как join в другом потоке - особенно не полностью бесполезный блокирует вызовы, такие как join ing daemon threads. Например, просто измените последний цикл в главном потоке на текущий (полный и разрушительный):

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

к чему-то более разумному, например:

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

если у вашего main нет ничего лучше, чем либо завершить все потоки самостоятельно, либо получить управляющий C (или другой сигнал).

Конечно, есть много других используемых шаблонов, если вы предпочитаете, чтобы ваши потоки не прерывались внезапно (как это могут быть демонические потоки) - если они тоже не навсегда прерваны в безусловно блокирующих вызовах , тупики и тому подобное; -).

16 голосов
/ 28 октября 2009

Существует два основных способа: один чистый и один простой.

Чистый способ - перехватить 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 (), который немедленно завершает все потоки.

5 голосов
/ 15 ноября 2011

A Рабочий может быть полезен для вас:

#!/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 голосов
/ 08 января 2015

Вы всегда можете установить свои потоки на потоки "daemon", такие как:

t.daemon = True
t.start()

И когда основной поток умирает, все потоки умирают вместе с ним.

http://www.regexprn.com/2010/05/killing-multithreaded-python-programs.html

4 голосов
/ 22 июня 2012

Я бы предпочел использовать код, предложенный в этом сообщении в блоге :

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.join с t.join (1) до t.join (1000) . Фактическое количество секунд не имеет значения, если вы не укажете номер таймаута, основной поток будет реагировать на Ctrl + C. Исключение KeyboardInterrupt делает обработку сигналов более явной.

1 голос
/ 12 сентября 2015
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)
1 голос
/ 14 августа 2013

Если вы создаете Нить как - myThread = Thread(target = function) - и затем делаете myThread.start(); myThread.join(). Когда инициируется CTRL-C, основной поток не завершает работу, поскольку ожидает этого блокирующего вызова myThread.join(). Чтобы это исправить, просто укажите время ожидания для вызова .join (). Тайм-аут может быть так долго, как вы хотите. Если вы хотите, чтобы ожидание длилось бесконечно, просто введите очень большой тайм-аут, например 99999. Также рекомендуется сделать myThread.daemon = True, чтобы все потоки выходили при выходе из основного потока (не демона).

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...