Есть ли способ убить нить? - PullRequest
666 голосов
/ 27 ноября 2008

Возможно ли завершить работающий поток без установки / проверки каких-либо флагов / семафоров / и т.

Ответы [ 24 ]

605 голосов
/ 28 ноября 2008

Обычно плохой шаблон - внезапное завершение потока на Python и на любом языке. Подумайте о следующих случаях:

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

Хороший способ справиться с этим, если вы можете себе это позволить (если вы управляете своими собственными потоками), - это иметь флаг exit_request, который каждый поток проверяет через регулярные промежутки времени, чтобы узнать, пора ли ему выйти.

Например:

import threading

class StoppableThread(threading.Thread):
    """Thread class with a stop() method. The thread itself has to check
    regularly for the stopped() condition."""

    def __init__(self):
        super(StoppableThread, self).__init__()
        self._stop_event = threading.Event()

    def stop(self):
        self._stop_event.set()

    def stopped(self):
        return self._stop_event.is_set()

В этом коде вы должны вызывать stop () в потоке, когда вы хотите, чтобы он завершился, и ждать, пока поток завершится правильно, с помощью join (). Поток должен регулярно проверять флаг остановки.

Однако бывают случаи, когда вам действительно нужно убить поток. Например, когда вы оборачиваете внешнюю библиотеку, которая занята для длительных вызовов, и хотите прервать ее.

Следующий код позволяет (с некоторыми ограничениями) вызвать исключение в потоке Python:

def _async_raise(tid, exctype):
    '''Raises an exception in the threads with id tid'''
    if not inspect.isclass(exctype):
        raise TypeError("Only types can be raised (not instances)")
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid),
                                                     ctypes.py_object(exctype))
    if res == 0:
        raise ValueError("invalid thread id")
    elif res != 1:
        # "if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"
        ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid), None)
        raise SystemError("PyThreadState_SetAsyncExc failed")

class ThreadWithExc(threading.Thread):
    '''A thread class that supports raising exception in the thread from
       another thread.
    '''
    def _get_my_tid(self):
        """determines this (self's) thread id

        CAREFUL : this function is executed in the context of the caller
        thread, to get the identity of the thread represented by this
        instance.
        """
        if not self.isAlive():
            raise threading.ThreadError("the thread is not active")

        # do we have it cached?
        if hasattr(self, "_thread_id"):
            return self._thread_id

        # no, look for it in the _active dict
        for tid, tobj in threading._active.items():
            if tobj is self:
                self._thread_id = tid
                return tid

        # TODO: in python 2.6, there's a simpler way to do : self.ident

        raise AssertionError("could not determine the thread's id")

    def raiseExc(self, exctype):
        """Raises the given exception type in the context of this thread.

        If the thread is busy in a system call (time.sleep(),
        socket.accept(), ...), the exception is simply ignored.

        If you are sure that your exception should terminate the thread,
        one way to ensure that it works is:

            t = ThreadWithExc( ... )
            ...
            t.raiseExc( SomeException )
            while t.isAlive():
                time.sleep( 0.1 )
                t.raiseExc( SomeException )

        If the exception is to be caught by the thread, you need a way to
        check that your thread has caught it.

        CAREFUL : this function is executed in the context of the
        caller thread, to raise an excpetion in the context of the
        thread represented by this instance.
        """
        _async_raise( self._get_my_tid(), exctype )

(На основе Killable Threads от Tomer Filiba. Цитата о возвращаемом значении PyThreadState_SetAsyncExc, похоже, взята из старой версии Python .)

Как отмечено в документации, это не волшебная палочка, потому что, если поток занят вне интерпретатора Python, он не будет перехватывать прерывание.

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

106 голосов
/ 27 ноября 2008

Официального API для этого нет, нет.

Вам нужно использовать API платформы, чтобы убить поток, например, pthread_kill или TerminateThread. Вы можете получить доступ к такому API, например, через pythonwin или через ctypes.

Обратите внимание, что это небезопасно. Скорее всего, это приведет к необратимому мусору (из локальных переменных стековых фреймов, которые становятся мусором) и может привести к взаимным блокировкам, если у удаляемого потока есть GIL в момент его завершения.

78 голосов
/ 13 октября 2011

A multiprocessing.Process может p.terminate()

В тех случаях, когда я хочу уничтожить поток, но не хочу использовать флаги / блокировки / сигналы / семафоры / события / что угодно, я продвигаю потоки в полномасштабные процессы. Для кода, который использует только несколько потоков, издержки не так уж и плохи.

например. это удобно, чтобы легко завершать вспомогательные «потоки», которые выполняют блокирующий ввод / вывод

Преобразование тривиально: в связанном коде замените все threading.Thread на multiprocessing.Process и все queue.Queue на multiprocessing.Queue и добавьте необходимые вызовы p.terminate() в родительский процесс, который хочет убить его дочерний элемент p

Python doc

62 голосов
/ 03 ноября 2009

Если вы пытаетесь завершить всю программу, вы можете установить поток как «демон». увидеть Thread.daemon

33 голосов
/ 07 марта 2013

Это основано на thread2 - убиваемые темы (рецепт Python)

Вам нужно вызвать PyThreadState_SetasyncExc (), который доступен только через ctypes.

Это было протестировано только на Python 2.7.3, но, скорее всего, оно будет работать с другими недавними выпусками 2.x.

import ctypes

def terminate_thread(thread):
    """Terminates a python thread from another thread.

    :param thread: a threading.Thread instance
    """
    if not thread.isAlive():
        return

    exc = ctypes.py_object(SystemExit)
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(
        ctypes.c_long(thread.ident), exc)
    if res == 0:
        raise ValueError("nonexistent thread id")
    elif res > 1:
        # """if it returns a number greater than one, you're in trouble,
        # and you should call it again with exc=NULL to revert the effect"""
        ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.ident, None)
        raise SystemError("PyThreadState_SetAsyncExc failed")
32 голосов
/ 27 ноября 2008

Вы никогда не должны принудительно убивать поток, не сотрудничая с ним.

Уничтожение потока снимает любые гарантии того, что блоки try / finally настроены так, что вы можете оставить блокировки заблокированными, открытые файлы и т. Д.

Единственный случай, когда вы можете утверждать, что принудительное уничтожение потоков - это хорошая идея, это быстрое уничтожение программы, но не отдельных потоков.

30 голосов
/ 03 декабря 2014

Как уже упоминалось, нормой является установка флага остановки. Для чего-то более легкого (без подклассов Thread, без глобальной переменной), лямбда-обратный вызов является опцией. (Обратите внимание на круглые скобки в if stop().)

import threading
import time

def do_work(id, stop):
    print("I am thread", id)
    while True:
        print("I am thread {} doing something".format(id))
        if stop():
            print("  Exiting loop.")
            break
    print("Thread {}, signing off".format(id))


def main():
    stop_threads = False
    workers = []
    for id in range(0,3):
        tmp = threading.Thread(target=do_work, args=(id, lambda: stop_threads))
        workers.append(tmp)
        tmp.start()
    time.sleep(3)
    print('main: done sleeping; time to stop the threads.')
    stop_threads = True
    for worker in workers:
        worker.join()
    print('Finis.')

if __name__ == '__main__':
    main()

Замена print() функцией pr(), которая всегда мигает (sys.stdout.flush()), может улучшить точность вывода оболочки.

(проверено только на Windows / Eclipse / Python3.3)

23 голосов
/ 03 марта 2013

В Python вы просто не можете уничтожить поток напрямую.

Если вам НЕ действительно нужен поток (!), Вместо использования threading пакета вы можете использовать многопроцессорный пакет . Здесь, чтобы убить процесс, вы можете просто вызвать метод:

yourProcess.terminate()  # kill the process!

Python убьет ваш процесс (в Unix через сигнал SIGTERM, в то время как в Windows через вызов TerminateProcess()). Обратите внимание, чтобы использовать его при использовании очереди или трубы! (это может повредить данные в очереди / канале)

Обратите внимание, что multiprocessing.Event и multiprocessing.Semaphore работают точно так же, как threading.Event и threading.Semaphore соответственно. На самом деле, первые - это клоны последних.

Если вам ДЕЙСТВИТЕЛЬНО нужно использовать поток, нет способа уничтожить его напрямую. Однако вы можете использовать "поток демона" . На самом деле, в Python поток может быть помечен как daemon :

yourThread.daemon = True  # set the Thread as a "daemon thread"

Основная программа завершит работу, когда не останется живых потоков, не являющихся демонами. Другими словами, когда ваш основной поток (который, конечно, не является потоком демона) завершит свои операции, программа завершит работу, даже если все еще работают некоторые потоки демона.

Обратите внимание, что необходимо установить Thread как daemon перед вызовом метода start()!

Конечно, вы можете и должны использовать daemon даже с multiprocessing. Здесь, когда основной процесс завершается, он пытается завершить все свои демонические дочерние процессы.

Наконец, обратите внимание, что sys.exit() и os.kill() не являются вариантами выбора.

13 голосов
/ 28 ноября 2008

Вы можете убить поток, установив трассировку в поток, который выйдет из потока. Смотрите прикрепленную ссылку для одной возможной реализации.

Убить нить в Python

9 голосов
/ 27 ноября 2008

Лучше, если вы не убьете нить. Можно было бы ввести блок try в цикл потока и вызвать исключение, когда вы хотите остановить поток (например, break / return / ..., который останавливает ваш for / while / ...). Я использовал это в своем приложении, и оно работает ...

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