Чи є спосіб вбити нитку?


Відповіді:


672

Це, як правило, поганий зразок, щоб різко вбивати нитку, на 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,  *args, **kwargs):
        super(StoppableThread, self).__init__(*args, **kwargs)
        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 )

(За матеріалами " Вбивчі нитки " Томера Філіба. Цитата про повернене значення, PyThreadState_SetAsyncExcздається, походить зі старої версії Python .)

Як зазначається в документації, це не чарівна куля, оскільки якщо нитка зайнята поза інтерпретатором Python, вона не вловить переривання.

Хороший шаблон використання цього коду полягає в тому, щоб потік вловлював конкретний виняток і проводив очищення. Таким чином, ви можете перервати завдання і все одно мати належне очищення.


78
@ Bluebird75: Крім того, я не впевнений, що я отримую аргумент, що нитки не повинні вбиватися різко, "оскільки в потоці може бути критичний ресурс, який повинен бути належним чином закритий": це також стосується основної програми та основних програм може бути вбито користувачем (наприклад, Ctrl-C в Unix) - в такому випадку вони намагаються максимально красиво обробити цю можливість. Отже, я не бачу, у чому особливість потоків, і чому вони не повинні отримувати таке ж лікування, як основні програми (а саме те, що їх можна вбити різко). :) Не могли б ви детальніше зупинитися на цьому?
Ерік О Лебігот

18
@EOL: З іншого боку, якщо всі ресурси, якими володіє потік, - це локальні ресурси (відкриті файли, сокети), Linux досить добре підходить до очищення процесу, і це не протікає. У мене були випадки, коли я створив сервер за допомогою сокета, і якщо я зроблю жорстоке переривання з Ctrl-C, я більше не можу запускати програму, оскільки вона не може зв’язати сокет. Мені потрібно почекати 5 хвилин. Правильним рішенням було захопити Ctrl-C і зробити чисте відключення розетки.
Філіп Ф

10
@ Bluebird75: btw. ви можете використовувати SO_REUSEADDRопцію socket, щоб уникнути Address already in useпомилок.
Messa

12
Зверніть увагу на цю відповідь: принаймні для мене (py2.6) мені довелося пройти Noneзамість 0цього res != 1випадку, і мені довелося зателефонувати ctypes.c_long(tid)та передати його будь-якій функції ctypes, а не безпосередньо tid.
Walt W

21
Варто зазначити, що _stop вже зайнятий у бібліотеці з потоками Python 3. Можливо, використовуйте іншу змінну, інакше ви отримаєте помилку.
померли періоди

113

Немає офіційного API для цього, ні.

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

Зауважте, що це по суті небезпечно. Це, ймовірно, призведе до неприбраного сміття (з локальних змінних кадрів стека, які стають сміттям), і може призвести до тупикових ситуацій, якщо вбита нитка має GIL в точці, коли вона буде вбита.


26
Це буде призводити до тупиків , якщо нитка в питанні тримає GIL.
Маттіас Урліхс

96

А multiprocessing.Processможеp.terminate()

У тих випадках, коли я хочу вбити нитку, але не хочу використовувати прапори / блокування / сигнали / семафори / події / будь-що інше, я просуваю нитки до повномасштабних процесів. Для коду, який використовує лише кілька потоків, накладні витрати не такі вже й погані.

Наприклад, це зручно для легкого припинення допоміжних "потоків", які виконують блокування вводу / виводу

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

Дивіться документаціюmultiprocessing на Python для .


Дякую. Я замінив чергу.Черу на багатопроцесорну. Доступна черга, і я відповів за цією відповіддю: stackoverflow.com/a/11984760/911207
Девід Браун

Дуже багато сторінок цього питання. Це здається очевидним рішенням для багатьох, я думаю,
геотеорія

6
multiprocessingприємно, але майте на увазі, що аргументи підбираються до нового процесу. Отже, якщо один із аргументів є неприйнятним (наприклад, а logging.log), це може бути недоброю ідеєю multiprocessing.
Лягер

1
multiprocessingаргументи підбираються до нового процесу в Windows, але Linux використовує forking для їх копіювання (Python 3.7, не знаючи, які інші версії). Таким чином, ви отримаєте код, який працює в Linux, але викликає помилки підберезника в Windows.
nyanpasu64

multiprocessingз лісозаготівлею - справа хитра. Потрібно скористатися QueueHandler(див. Цей підручник ). Я навчився цього важким шляхом.
Fanchen Bao

74

Якщо ви намагаєтеся закінчити всю програму, ви можете встановити потік як "демон". див. Thread.daemon


Це не має сенсу. У документації чітко зазначено: "Це потрібно встановити до виклику start (), інакше RuntimeError буде піднятий." Таким чином, якщо я хочу вбити нитку, яка спочатку не була демоном, як я можу це використати?
Раффі Хатчадуріан

27
Раффі, я думаю, що він пропонує вам встановити його заздалегідь, знаючи, що при виході вашої основної нитки ви також хочете виходити з демонових ниток.
фантастичний

1
Не впевнений, чому це не прийнята відповідь
eric

Чи не встановлює нитку як демон, що б ви зробили, якщо ви хочете, щоб нитка продовжувала працювати, навіть якщо ваша основна програма вимикається?
Мішель Піколіні

Ви, сер, мій герой дня. Саме те, що я шукав, і ніякої суєти взагалі не додавати.
Blizz

42

Як вже згадували інші, норма - встановити прапор зупинки. Для чогось легкого (без підкласу 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)


1
Перевірено на Linux / Python 2.7, працює як шарм. Це має бути офіційна відповідь, це набагато простіше.
Пол Кеньора

1
Перевірено на Linux Ubuntu Server 17.10 / Python 3.6.3 і працює на ньому.
Маркос

1
Також перевірено в 2.7. Така приємна відповідь!
silgon

Що таке pr()функція?
альпер

35

Це засновано на нитках2, які можна захистити (рецепт 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")

Я використовую щось подібне, щоб дати свої теми, KeyboardInterruptщоб вони мали можливість очистити. Якщо вони все ще звисають після цього, то SystemExitце доречно або просто вбити процес з терміналу.
drevicko

Це працює, якщо потоку в даний час виконується. Це не працює, якщо нитка знаходиться в системному дзвінку; виняток буде мовчки ігноруватися.
Маттіас Урлічс

@MatthiasUrlich має будь-яку ідею, як визначити, що таке стан виконання потоку, щоб можна було надрукувати попередження або повторити спробу?
Йохан Далін

1
@JohanDahlin Ви можете трохи почекати (що, якщо ви хочете спробувати, все одно потрібно зробити), а потім зробити тест isAlive (). У будь-якому випадку, хоча це буде працювати, я також не гарантую, що він не залишатиме звисаючих посилань. Хоча теоретично можна зробити вбивство потоків безпечним у CPython, розумним використанням pthread_cleanup_push()/_pop()цього було б багато роботи, щоб правильно реалізувати, і це помітно сповільнить інтерпретатора.
Маттіас Урліхс

32

Ніколи не слід насильно вбивати нитку, не співпрацюючи з нею.

Убиваючи потік, знімаються гарантії, що спроба / остаточно блокується налаштована, щоб ви могли залишити замок заблокованим, файли відкритими тощо.

Єдиний раз, коли ви можете стверджувати, що примусово вбивати теми - це гарна ідея - це вбити програму швидко, але ніколи не поодиноко.


11
Чому так важко просто сказати нитку, будь ласка, вбийте себе, коли закінчите свій поточний цикл ... Я не розумію.
Мехді

2
У процесорі немає вбудованого механізму, який би ідентифікував "цикл" як такий, найкраще, на що ви можете сподіватися, - це використовувати якийсь сигнал, який код, який зараз знаходиться всередині циклу, перевірятиме після його виходу. Правильний спосіб обробки синхронізації потоків - це кооперативні засоби, призупинення, відновлення та вбивство потоків - це функції, призначені для налагоджувачів та операційної системи, а не код програми.
Лассе В. Карлсен

2
@Mehdi: якщо я (особисто) пишу код у нитці, так, я згоден з вами. Але бувають випадки, коли я запускаю сторонні бібліотеки, і я не маю доступу до циклу виконання цього коду. Це один випадок використання запитуваної функції.
Dan H

@DanH Це навіть найгірше з стороннім кодом, оскільки ви поняття не маєте, який збиток він може завдати. Якщо ваша стороння бібліотека не є достатньо надійною, щоб її вимагали вбити, то вам слід зробити одне із них: (1) попросити автора виправити проблему, (2) скористатися чимось іншим. Якщо у вас справді немає вибору, то введення цього коду в окремий процес має бути безпечнішим, оскільки деякі ресурси діляться лише в рамках одного процесу.
Phil1970

25

У Python ви просто не можете вбити нитку безпосередньо.

Якщо вам НЕ дійсно потрібно мати нитку (!), То замість того, щоб використовувати пакет для нарізки , ви можете зробити, щоб використовувати багатопроцесорний пакет . Тут, щоб вбити процес, ви можете просто викликати метод:

yourProcess.terminate()  # kill the process!

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

Зауважимо, що multiprocessing.Eventі multiprocessing.Semaphoreробота точно так само, як threading.Eventі threading.Semaphoreвідповідно. Насправді перші - це клони латерів.

Якщо вам дійсно потрібно використовувати нитку, немає способу вбити її безпосередньо. Однак ви можете скористатися "демоновою ниткою" . Насправді, у Python, Нитка може бути позначена як демон :

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

Основна програма вийде, коли не залишиться жодної живої недемонової нитки. Іншими словами, коли ваша основна нитка (це, звичайно, недемонова нитка) завершить свої операції, програма вийде, навіть якщо ще працюють деякі демонові нитки.

Зауважте, що необхідно встановити Thread так, як daemonраніше start()називався метод!

Звичайно, можна і потрібно використовувати daemonнавіть з multiprocessing. Тут, коли основний процес закінчується, він намагається припинити всі свої демонічні дочірні процеси.

Нарешті, будь ласка, зверніть увагу , що sys.exit()і os.kill()не вибір.


14

Ви можете вбити потік, встановивши слід у нитку, яка вийде з потоку. Дивіться додане посилання для однієї можливої ​​реалізації.

Вбийте нитку в Python


Я вже бачив це. Це рішення ґрунтується на перевірці прапора самоврядування
Sudden Def

1
Один з небагатьох відповідей тут, що насправді РОБОТИ
Ponkadoodle

5
Дві проблеми з цим рішенням: (a) встановлення трекера з sys.settrace () зробить ваш потік повільніше. У 10 разів повільніше, якщо вона обчислена. (b) не вплине на ваш потік, поки він знаходиться в системному дзвінку.
Маттіас Урліхс

10

Якщо ви явно телефонуєте time.sleep()як частину своєї потоку (скажімо, опитуєте якусь зовнішню службу), поліпшенням методу Філліпа є використання тайм-ауту в методі events, wait()де виsleep()

Наприклад:

import threading

class KillableThread(threading.Thread):
    def __init__(self, sleep_interval=1):
        super().__init__()
        self._kill = threading.Event()
        self._interval = sleep_interval

    def run(self):
        while True:
            print("Do Something")

            # If no kill signal is set, sleep for the interval,
            # If kill signal comes in while sleeping, immediately
            #  wake up and handle
            is_killed = self._kill.wait(self._interval)
            if is_killed:
                break

        print("Killing Thread")

    def kill(self):
        self._kill.set()

Потім запустити його

t = KillableThread(sleep_interval=5)
t.start()
# Every 5 seconds it prints:
#: Do Something
t.kill()
#: Killing Thread

Перевага використання wait()замість sleep()ing та регулярної перевірки події полягає в тому, що ви можете програмувати в більш тривалих інтервалах сну, нитка зупиняється майже негайно (коли ви б інакше sleep()працювали), і на мою думку, код для обробки виходу значно простіший .


3
чому цю посаду було скасовано? Що не так з цією публікацією? Це схоже на те, що мені потрібно ....
JDOaktown

9

Краще, якщо ви не вб'єте нитку. Способом може бути введення блоку "спробувати" в цикл потоку і викинути виняток, коли ви хочете зупинити потік (наприклад, перерва / повернення / ..., що зупиняє ваш час / час / ...). Я використовував це у своєму додатку, і це працює ...


8

Однозначно можливо реалізувати Thread.stopметод, як показано в наступному прикладі коду:

import sys
import threading
import time


class StopThread(StopIteration):
    pass

threading.SystemExit = SystemExit, StopThread


class Thread2(threading.Thread):

    def stop(self):
        self.__stop = True

    def _bootstrap(self):
        if threading._trace_hook is not None:
            raise ValueError('Cannot run thread with tracing!')
        self.__stop = False
        sys.settrace(self.__trace)
        super()._bootstrap()

    def __trace(self, frame, event, arg):
        if self.__stop:
            raise StopThread()
        return self.__trace


class Thread3(threading.Thread):

    def _bootstrap(self, stop_thread=False):
        def stop():
            nonlocal stop_thread
            stop_thread = True
        self.stop = stop

        def tracer(*_):
            if stop_thread:
                raise StopThread()
            return tracer
        sys.settrace(tracer)
        super()._bootstrap()

###############################################################################


def main():
    test1 = Thread2(target=printer)
    test1.start()
    time.sleep(1)
    test1.stop()
    test1.join()
    test2 = Thread2(target=speed_test)
    test2.start()
    time.sleep(1)
    test2.stop()
    test2.join()
    test3 = Thread3(target=speed_test)
    test3.start()
    time.sleep(1)
    test3.stop()
    test3.join()


def printer():
    while True:
        print(time.time() % 1)
        time.sleep(0.1)


def speed_test(count=0):
    try:
        while True:
            count += 1
    except StopThread:
        print('Count =', count)

if __name__ == '__main__':
    main()

Схоже, Thread3клас запускає код приблизно на 33% швидше, ніж Thread2клас.


3
Це розумний спосіб вставити чеки self.__stopна наявність потоку. Зауважте, що, як і більшість інших рішень тут, він фактично не перериває блокуючий виклик, оскільки функція відстеження викликається лише при введенні нової локальної області. Також варто відзначити, що sys.settraceдійсно призначений для реалізації налагоджувачів, профілів тощо, і як такий вважається деталі реалізації CPython, і не гарантується його існування в інших реалізаціях Python.
Дано

3
@dano: Однією з найбільших проблем Thread2класу є те, що він запускає код приблизно в десять разів повільніше. Деякі люди все ще можуть вважати це прийнятним.
Noctis Skytower

+1 щодо цього значно сповільнює виконання коду .. Я б запропонував автору цього рішення включити цю інформацію у відповідь.
Вішал

6
from ctypes import *
pthread = cdll.LoadLibrary("libpthread-2.15.so")
pthread.pthread_cancel(c_ulong(t.ident))

t - ваш Threadоб’єкт.

Прочитайте джерело python ( Modules/threadmodule.cта Python/thread_pthread.h), яке ви бачите Thread.ident, це pthread_tтип, тож ви можете зробити все, що pthreadможна зробити, використовуючи python libpthread.


І як це зробити в Windows?
iChux

12
Ви цього не робите; не в Windows і не в Linux. Причина: Потік, про який йде мова, може містити GIL, коли ви це робите (Python випускає GIL при виклику в C). Якщо це станеться, ваша програма миттєво зайде в глухий кут. Навіть якщо цього не відбудеться, нарешті: блоки не виконуватимуться тощо, тож це дуже небезпечна ідея.
Маттіас Урлічс

6

Для усунення потоку може бути використаний наступний спосіб вирішення:

kill_threads = False

def doSomething():
    global kill_threads
    while True:
        if kill_threads:
            thread.exit()
        ......
        ......

thread.start_new_thread(doSomething, ())

Це можна використовувати навіть для завершення потоків, код яких записаний в іншому модулі, з основного потоку. Ми можемо оголосити глобальну змінну в цьому модулі і використовувати її для завершення потоку / с, породженого в цьому модулі.

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


Пальці вгору. Просте для розуміння.
alyssaeliyah

6

Я запізнююсь із цією грою, але я вела боротьбу з подібним питанням, і наступне, здається, обидва вирішує проблему ідеально для мене І дозволяє мені виконати деякі основні перевірки стану та потоку, коли очищений підрядний потік виходить:

import threading
import time
import atexit

def do_work():

  i = 0
  @atexit.register
  def goodbye():
    print ("'CLEANLY' kill sub-thread with value: %s [THREAD: %s]" %
           (i, threading.currentThread().ident))

  while True:
    print i
    i += 1
    time.sleep(1)

t = threading.Thread(target=do_work)
t.daemon = True
t.start()

def after_timeout():
  print "KILL MAIN THREAD: %s" % threading.currentThread().ident
  raise SystemExit

threading.Timer(2, after_timeout).start()

Врожайність:

0
1
KILL MAIN THREAD: 140013208254208
'CLEANLY' kill sub-thread with value: 2 [THREAD: 140013674317568]

Це відмінна відповідь, яка повинна бути вище в списку
drootang

Чому підняття SystemExitна after_timeoutнитку може робити що-небудь з основною ниткою (яка просто чекає колишнього виходу в цьому прикладі)?
Девіс Оселедець

@DavisHerring Я не впевнений, що ти отримуєш. SystemExit вбиває основний потік, чому ви вважаєте, що НЕ БУДЕ щось робити на головній нитці? Без цього дзвінка програма просто продовжить чекати на дочірній нитці. Ви також можете ctrl + c або використовувати будь-які інші засоби для вбивства головної нитки, але це приклад.
slumtrimpet

@slumtrimpet: SystemExitмає лише два особливих властивості: він не створює прослідкування (коли будь-який потік виходить за допомогою викидання одного), а якщо основний потік виходить, перекинувши його, він встановлює статус виходу (при цьому чекаючи інших недемонних потоків для виходу).
Оселедець Девіса

@DavisHerring О, я отримав. Повністю згоден з вами. Я неправильно пам'ятав, що ми тут робили, і неправильно розумів ваш коментар.
slumtrimpet

4

Я хочу додати одне, що якщо ви читаєте офіційну документацію в потоці lib Python , рекомендується уникати використання "демонічних" ниток, коли ви не хочете, щоб теми різко закінчувалися, прапором, який згадував Паоло Ровеллі .

З офіційної документації:

Нитки Демона різко зупиняються при відключенні. Їх ресурси (наприклад, відкриті файли, транзакції бази даних тощо) можуть не бути належним чином випущені. Якщо ви хочете, щоб ваші потоки граціозно зупинялися, зробіть їх недемонічними та використовуйте відповідний механізм сигналізації, такий як подія.

Я думаю, що створення демонічних ниток залежить від вашої програми, але в цілому (і на мою думку) краще уникати їх вбивання або створення їх демонічними. У мультиобробці ви можете використовуватиis_alive() перевірити стан процесу та "припинити" їх закінчення (Також ви уникнете проблем з GIL). Але іноді ви можете знайти більше проблем під час виконання коду в Windows.

І завжди пам’ятайте, що якщо у вас є "живі нитки", інтерпретатор Python буде працювати на їх очікування. (Через це демон може допомогти вам, якщо все-таки різко не закінчиться).


4
Я не розумію останнього абзацу.
tshepang

@Tshepang Це означає, що якщо у вашій програмі є якісь запущені недемонічні потоки, інтерпретатор Python продовжуватиме працювати до тих пір, поки не будуть виконані всі недемонові потоки . Якщо вам все одно, чи закінчуються нитки, коли програма закінчується, то їх використання може бути корисним.
Том Мидделтин

3

Для цього побудована бібліотека, стопіт . Хоча деякі із тих самих застережень, які перераховані в цьому документі, все ще застосовуються, принаймні ця бібліотека представляє звичайну, повторювану методику досягнення поставленої мети.


1

Хоча це досить старе, для деяких це може бути зручним рішенням:

Маленький модуль, який розширює функціональність модуля нитки - дозволяє одному потоку збирати винятки в контексті іншого потоку. Піднімаючи SystemExit, ви можете остаточно вбити нитки пітона.

import threading
import ctypes     

def _async_raise(tid, excobj):
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(tid, ctypes.py_object(excobj))
    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(tid, 0)
        raise SystemError("PyThreadState_SetAsyncExc failed")

class Thread(threading.Thread):
    def raise_exc(self, excobj):
        assert self.isAlive(), "thread must be started"
        for tid, tobj in threading._active.items():
            if tobj is self:
                _async_raise(tid, excobj)
                return

        # the thread was alive when we entered the loop, but was not found 
        # in the dict, hence it must have been already terminated. should we raise
        # an exception here? silently ignore?

    def terminate(self):
        # must raise the SystemExit type, instead of a SystemExit() instance
        # due to a bug in PyThreadState_SetAsyncExc
        self.raise_exc(SystemExit)

Отже, це дозволяє "потоці підняти винятки в контексті іншого потоку", і таким чином завершений потік може обробляти припинення, не регулярно перевіряючи прапор переривання.

Однак, за його первинним джерелом , з цим кодом є деякі проблеми.

  • Виняток буде піднято лише при виконанні байт-коду python. Якщо ваш потік викликає нативну / вбудовану функцію блокування, виняток буде підвищено лише тоді, коли виконання повернеться до коду python.
    • Існує також проблема, якщо вбудована функція внутрішньо викликає PyErr_Clear (), що фактично скасує очікуване виключення. Ви можете спробувати підняти його знову.
  • Лише типи винятків можна безпечно підвищувати. Випадки винятку, ймовірно, можуть спричинити несподівану поведінку, і тому обмежуються.
  • Я попросив розкрити цю функцію у вбудованому модулі потоку, але оскільки ctypes став стандартною бібліотекою (станом на 2.5), і ця
    функція, швидше за все, не буде аггностичною, вона може залишатися
    невизначеною.

0

Схоже, це працює з pywin32 на Windows 7

my_thread = threading.Thread()
my_thread.start()
my_thread._Thread__stop()

0

Пітер Хінтьенс - один із засновників ØMQ - говорить, що використання ØMQ та уникнення примітивів синхронізації, таких як замки, мютекси, події тощо, є найбезпечнішим і найбезпечнішим способом написання багатопотокових програм:

http://zguide.zeromq.org/py:all#Multithreading-with-ZeroMQ

Сюди входить сказати дочірній нитці, що вона повинна скасувати свою роботу. Це можна зробити, оснастивши потік ØMQ-сокетом і опитуючи на цьому сокеті повідомлення про те, що він повинен скасувати.

Посилання також надає приклад багатопотокового коду python з ØMQ.


0

Припускаючи, що ви хочете мати кілька потоків однієї і тієї ж функції, це IMHO найпростіша реалізація, щоб зупинити її на id:

import time
from threading import Thread

def doit(id=0):
    doit.stop=0
    print("start id:%d"%id)
    while 1:
        time.sleep(1)
        print(".")
        if doit.stop==id:
            doit.stop=0
            break
    print("end thread %d"%id)

t5=Thread(target=doit, args=(5,))
t6=Thread(target=doit, args=(6,))

t5.start() ; t6.start()
time.sleep(2)
doit.stop =5  #kill t5
time.sleep(2)
doit.stop =6  #kill t6

Приємно, що тут ви можете мати декілька одних і тих же і різних функцій, і зупинити їх на всіх functionname.stop

Якщо ви хочете мати лише один потік функції, тоді вам не потрібно пам’ятати ідентифікатор. Просто зупиніться, якщо doit.stop> 0.


0

Просто для створення ідеї @ SCB (що саме було мені потрібно), щоб створити підклас KillableThread з налаштованою функцією:

from threading import Thread, Event

class KillableThread(Thread):
    def __init__(self, sleep_interval=1, target=None, name=None, args=(), kwargs={}):
        super().__init__(None, target, name, args, kwargs)
        self._kill = Event()
        self._interval = sleep_interval
        print(self._target)

    def run(self):
        while True:
            # Call custom function with arguments
            self._target(*self._args)

        # If no kill signal is set, sleep for the interval,
        # If kill signal comes in while sleeping, immediately
        #  wake up and handle
        is_killed = self._kill.wait(self._interval)
        if is_killed:
            break

    print("Killing Thread")

def kill(self):
    self._kill.set()

if __name__ == '__main__':

    def print_msg(msg):
        print(msg)

    t = KillableThread(10, print_msg, args=("hello world"))
    t.start()
    time.sleep(6)
    print("About to kill thread")
    t.kill()

Звичайно, як і у @SBC, потік не чекає запуску нового циклу, щоб зупинитися. У цьому прикладі ви побачите повідомлення "Вбивча нитка", надруковане відразу після "Про те, щоб вбити нитку", замість того, щоб чекати ще 4 секунди, щоб нитка завершилася (оскільки ми проспали вже 6 секунд).

Другий аргумент у конструкторі KillableThread - це ваша спеціальна функція (print_msg тут). Аргумент Args - це аргументи, які будуть використовуватися при виклику функції (("привіт світ")) тут.


0

Як уже згадувалося в @ Kozyarchuk в відповідь , установка трасування працює. Оскільки ця відповідь не містила коду, ось робочий приклад, готовий до використання:

import sys, threading, time 

class TraceThread(threading.Thread): 
    def __init__(self, *args, **keywords): 
        threading.Thread.__init__(self, *args, **keywords) 
        self.killed = False
    def start(self): 
        self._run = self.run 
        self.run = self.settrace_and_run
        threading.Thread.start(self) 
    def settrace_and_run(self): 
        sys.settrace(self.globaltrace) 
        self._run()
    def globaltrace(self, frame, event, arg): 
        return self.localtrace if event == 'call' else None
    def localtrace(self, frame, event, arg): 
        if self.killed and event == 'line': 
            raise SystemExit() 
        return self.localtrace 

def f(): 
    while True: 
        print('1') 
        time.sleep(2)
        print('2') 
        time.sleep(2)
        print('3') 
        time.sleep(2)

t = TraceThread(target=f) 
t.start() 
time.sleep(2.5) 
t.killed = True

Він припиняється після друку 1та 2. 3не друкується.


-1

Ви можете виконати свою команду в процесі, а потім вбити її за допомогою ідентифікатора процесу. Мені потрібно було синхронізувати між двома потоками, одна з яких не повертається сама собою.

processIds = []

def executeRecord(command):
    print(command)

    process = subprocess.Popen(command, stdout=subprocess.PIPE)
    processIds.append(process.pid)
    print(processIds[0])

    #Command that doesn't return by itself
    process.stdout.read().decode("utf-8")
    return;


def recordThread(command, timeOut):

    thread = Thread(target=executeRecord, args=(command,))
    thread.start()
    thread.join(timeOut)

    os.kill(processIds.pop(), signal.SIGINT)

    return;

-1

Запустіть підрядний потік з setDaemon (True).

def bootstrap(_filename):
    mb = ModelBootstrap(filename=_filename) # Has many Daemon threads. All get stopped automatically when main thread is stopped.

t = threading.Thread(target=bootstrap,args=('models.conf',))
t.setDaemon(False)

while True:
    t.start()
    time.sleep(10) # I am just allowing the sub-thread to run for 10 sec. You can listen on an event to stop execution.
    print('Thread stopped')
    break

-2

Це погана відповідь, дивіться коментарі

Ось як це зробити:

from threading import *

...

for thread in enumerate():
    if thread.isAlive():
        try:
            thread._Thread__stop()
        except:
            print(str(thread.getName()) + ' could not be terminated'))

Дайте йому кілька секунд, тоді ваша нитка повинна бути зупинена. Перевірте також thread._Thread__delete()метод.

Я б рекомендував thread.quit()метод для зручності. Наприклад, якщо у вас є розетка в потоці, я рекомендую створити quit()метод у вашому класі рукоятки сокета, скасувати сокет і запустити thread._Thread__stop()внутрішню частину вашого quit().


Мені довелося використовувати self._Thread__stop () всередині моїх потоків. Об'єкт Thread, щоб зупинити його. Я не розумію, чому простий self.join (), як цей приклад ( code.activestate.com/recipes/65448-thread-control-idiom ) не працює.
harijay

12
Детальніше про тему "це дійсно не зупиняє нитку" було б корисно.
2371 р.

19
В основному, виклик методу _Thread__stop не має ефекту, окрім того, щоб сказати Python, що нитка зупинена. Він насправді може продовжувати працювати. Дивіться приклад на gist.github.com/2787191 .
Блюхорн

35
Це явно неправильно. _Thread__stop()просто позначає нитку як зупинену , вона фактично не зупиняє нитку! Ніколи цього не робіть. Прочитайте .
dotancohen

-2

Якщо вам справді потрібна можливість вбити підзадачу, використовуйте альтернативну реалізацію. multiprocessingі geventобидва підтримують без розбору вбиття "нитки".

Нарізка Python не підтримує скасування. Навіть не намагайся. Ваш код, ймовірно, може зайти в тупик, пошкодити або просочити пам'ять або мати інші непередбачувані "цікаві" важкі для налагодження ефекти, які трапляються рідко і недетерміновано.


2
... і так, я знаю, що обидва не строго "нанизують", але вони обидва працюють, якщо ваш код відповідає (або може бути зроблений відповідно до) їх моделі.
Маттіас Урліхс
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.