Czy jest jakiś sposób na zabicie Wątku?


Odpowiedzi:


672

Zasadniczo nagłe zabicie wątku w Pythonie i dowolnym języku jest złym wzorcem. Pomyśl o następujących przypadkach:

  • wątek zawiera krytyczny zasób, który musi zostać poprawnie zamknięty
  • wątek stworzył kilka innych wątków, które również muszą zostać zabite.

Dobrym sposobem na poradzenie sobie z tym, jeśli możesz sobie na to pozwolić (jeśli zarządzasz własnymi wątkami), jest ustawienie flagi exit_request, którą każdy wątek sprawdza w regularnych odstępach czasu, aby sprawdzić, czy nadszedł czas na zakończenie.

Na przykład:

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()

W tym kodzie powinieneś wywołać stop()wątek, gdy chcesz go zakończyć, i poczekać, aż wątek poprawnie się zakończy join(). Wątek powinien regularnie sprawdzać flagę stop.

Są jednak przypadki, kiedy naprawdę musisz zabić wątek. Przykładem jest owijanie biblioteki zewnętrznej, która jest zajęta długimi połączeniami i chcesz ją przerwać.

Poniższy kod pozwala (z pewnymi ograniczeniami) na zgłoszenie wyjątku w wątku 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 )

(Na podstawie Killable Threads autorstwa Tomera Filiby. Cytat o wartości zwracanej przez PyThreadState_SetAsyncExcwydaje się, że pochodzi ze starej wersji Pythona .)

Jak zauważono w dokumentacji, nie jest to magiczna kula, ponieważ jeśli wątek jest zajęty poza interpreterem Pythona, nie złapie przerwy.

Dobrym wzorcem użycia tego kodu jest, aby wątek przechwycił określony wyjątek i wykonał czyszczenie. W ten sposób możesz przerwać zadanie i nadal mieć właściwe czyszczenie.


78
@ Bluebird75: Co więcej, nie jestem pewien, czy dostaję argument, że wątki nie powinny zostać nagle zabite „ponieważ wątek może zawierać krytyczny zasób, który musi zostać poprawnie zamknięty”: dotyczy to również programu głównego i programów głównych może zostać nagle zabity przez użytkownika (na przykład Ctrl-C w Uniksie) - w takim przypadku starają się jak najlepiej wykorzystać tę możliwość. Nie rozumiem więc, co jest specjalnego w wątkach i dlaczego nie powinny być traktowane tak samo jak programy główne (a mianowicie, że mogą zostać nagle zabite). :) Czy mógłbyś to rozwinąć?
Eric O Lebigot,

18
@EOL: Z drugiej strony, jeśli wszystkie zasoby, których właścicielem jest wątek, są zasobami lokalnymi (otwarte pliki, gniazda), Linux jest dość dobry w czyszczeniu procesu i to nie przecieka. Miałem jednak przypadki, w których utworzyłem serwer za pomocą gniazda i jeśli zrobię brutalną przerwę z Ctrl-C, nie będę mógł dłużej uruchamiać programu, ponieważ nie może on powiązać gniazda. Muszę poczekać 5 minut. Właściwym rozwiązaniem było złapanie Ctrl-C i wykonanie czystego rozłączenia gniazda.
Philippe F

10
@ Bluebird75: btw. możesz użyć SO_REUSEADDRopcji gniazda, aby uniknąć Address already in usebłędu.
Messa,

12
Uwaga na temat tej odpowiedzi: przynajmniej dla mnie (py2.6) musiałem przekazać Nonezamiast 0w res != 1przypadku i musiałem to wywołać ctypes.c_long(tid)i przekazać do dowolnej funkcji ctyp, a nie bezpośrednio do TID.
Walt W

21
Warto wspomnieć, że _stop jest już zajęty w bibliotece wątków Pythona 3. Jako taki, może użyj innej zmiennej, inaczej pojawi się błąd.
deadtreetimes

113

Nie ma do tego oficjalnego API, nie.

Musisz użyć interfejsu API platformy, aby zabić wątek, np. Pthread_kill lub TerminateThread. Możesz uzyskać dostęp do takiego API np. Przez pythonwin lub poprzez ctypy.

Zauważ, że jest to z natury niebezpieczne. Prawdopodobnie doprowadzi to do nieściągalnego śmieci (z lokalnych zmiennych ramek stosu, które stają się śmieciami) i może doprowadzić do impasu, jeśli zabity wątek ma GIL w momencie, w którym został zabity.


26
To będzie prowadzić do zakleszczenia jeśli wątek w pytaniu posiada GIL.
Matthias Urlichs,

96

multiprocessing.Processpuszkap.terminate()

W przypadkach, w których chcę zabić wątek, ale nie chcę używać flag / blokad / sygnałów / semaforów / zdarzeń / czegokolwiek, promuję wątki do pełnych procesów. W przypadku kodu, który wykorzystuje tylko kilka wątków, narzut nie jest taki zły.

Np. Przydaje się to do łatwego kończenia „wątków” pomocnika, które wykonują blokujące We / Wy

Konwersja jest trywialny: W powiązanym kodzie zastąpić wszystko threading.Threadz multiprocessing.Processa wszystko queue.Queuez multiprocessing.Queuei dodać wymagane nawoływania p.terminate()do procesu macierzystego, który chce zabić swoje dzieckop

Zobacz dokumentację Pythona dlamultiprocessing .


Dzięki. Zamieniłem queue.Queue na multiprocessing.JoinableQueue i podążyłem za tą odpowiedzią: stackoverflow.com/a/11984760/911207
David Braun

Wiele stron na ten temat. Wydaje mi się to oczywistym rozwiązaniem dla wielu
geotheory

6
multiprocessingjest fajny, ale należy pamiętać, że argumenty są marynowane w nowym procesie. Więc jeśli jeden z argumentów jest niemożliwy do zaakceptowania (jak a logging.log), może nie być dobrym pomysłem multiprocessing.
Lyager,

1
multiprocessingargumenty są marynowane w nowym procesie w systemie Windows, ale Linux używa forking do ich skopiowania (Python 3.7, nie wiadomo, jakie inne wersje). Skończysz z kodem, który działa w systemie Linux, ale podnosi błędy w systemie Windows.
nyanpasu64

multiprocessinglogowanie jest trudnym biznesem. Musisz użyć QueueHandler(zobacz ten samouczek ). Nauczyłem się tego na własnej skórze.
Fanchen Bao,

74

Jeśli próbujesz zakończyć cały program, możesz ustawić wątek jako „demon”. zobacz Thread.daemon


To nie ma sensu. Dokumentacja wyraźnie stwierdza: „należy to ustawić przed wywołaniem start (), w przeciwnym razie wywoływany jest RuntimeError”. Jeśli więc chcę zabić wątek, który nie był pierwotnie demonem, jak mogę tego użyć?
Raffi Khatchadourian

27
Raffi Myślę, że sugeruje, abyś ustawił to z góry, wiedząc, że kiedy twój główny wątek wyjdzie, również chcesz, aby demony się zamknęły.
fantastyczny

1
Nie jestem pewien, dlaczego nie jest to akceptowana odpowiedź
eryczny

Czy ustawianie wątku jako demona nie jest czymś, co zrobiłbyś na wypadek, gdyby wątek nadal działał, nawet jeśli główny program zostanie zamknięty?
Michele Piccolini

Proszę pana, jesteś moim bohaterem dnia. Dokładnie to, czego szukałem, i nie ma problemu, aby dodać.
Blizz

42

Jak wspomnieli inni, normą jest ustawienie flagi zatrzymania. W przypadku czegoś lekkiego (bez podklasy wątku, bez zmiennej globalnej) opcja zwrotna lambda jest opcją. (Uwaga w nawiasach 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()

Wymiana print()z pr()funkcji, która zawsze opróżnia ( sys.stdout.flush()) może poprawić dokładność wyjścia powłoki.

(Testowane tylko w systemie Windows / Eclipse / Python3.3)


1
Zweryfikowany na Linux / Python 2.7, działa jak urok. To powinna być oficjalna odpowiedź, jest o wiele prostsza.
Paul Kenjora,

1
Zweryfikowany na Linux Ubuntu Server 17.10 / Python 3.6.3 i działa.
Marcos,

1
Sprawdzono także w 2.7. Niezła odpowiedź!
silgon

Co to jest pr()funkcja?
alper

35

Opiera się to na wątku 2 - wątki możliwe do wywołania (przepis na Python)

Musisz wywołać PyThreadState_SetasyncExc (), która jest dostępna tylko za pośrednictwem ctypów.

Zostało to przetestowane tylko w Pythonie 2.7.3, ale prawdopodobnie będzie działać z innymi najnowszymi wydaniami 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")

Używam czegoś takiego, aby dać swoim wątkom szansę, KeyboardInterruptaby miały szansę oczyścić. Jeśli nadal wiszą po tym, to SystemExitjest odpowiednie, lub po prostu zabij proces z terminala.
drevicko

Działa to, jeśli wątek jest aktualnie wykonywany. Nie działa, jeśli wątek jest w wywołaniu systemowym; wyjątek zostanie po cichu zignorowany.
Matthias Urlichs

@MatthiasUrlichs jakiś pomysł, jak wykryć stan wykonania wątku, aby móc wydrukować ostrzeżenie lub spróbować ponownie?
Johan Dahlin,

1
@JohanDahlin Możesz trochę poczekać (co, jeśli chcesz spróbować ponownie, i tak musisz zrobić), a następnie wykonaj test isAlive (). W każdym razie, chociaż to by działało, nie gwarantowałbym też, że nie pozostawiają wiszących referencji. Chociaż teoretycznie możliwe jest bezpieczne zabijanie wątków w CPython, przez rozsądne użycie, włożenie pthread_cleanup_push()/_pop()poprawnej implementacji wymagałoby dużo pracy i znacznie spowolniłoby interpretera.
Matthias Urlichs,

32

Nigdy nie powinieneś siłą zabijać nici bez współpracy z nim.

Zabicie wątku usuwa wszelkie gwarancje, które próbują / ostatecznie blokują skonfigurowane, więc możesz zostawić blokady zamknięte, pliki otwarte itp.

Jedyny raz, kiedy można argumentować, że przymusowe zabijanie wątków jest dobrym pomysłem, to szybkie zabicie programu, ale nigdy pojedynczych wątków.


11
Dlaczego tak trudno jest po prostu powiedzieć wątku, proszę zabij się, kiedy skończysz swoją obecną pętlę ... Nie rozumiem.
Mehdi,

2
W procesorze nie ma wbudowanego mechanizmu służącego do identyfikowania „pętli” jako takiej, najlepsze, na co możesz mieć nadzieję, to użyć pewnego rodzaju sygnału, który kod, który jest obecnie w pętli, sprawdzi po jej wyjściu. Prawidłowym sposobem obsługi synchronizacji wątków jest współpraca, zawieszanie, wznawianie i zabijanie wątków to funkcje przeznaczone dla debuggerów i systemu operacyjnego, a nie kod aplikacji.
Lasse V. Karlsen

2
@ Mehdi: jeśli (osobiście) piszę kod w wątku, tak, zgadzam się z tobą. Ale są przypadki, w których uruchamiam biblioteki stron trzecich i nie mam dostępu do pętli wykonawczej tego kodu. To jest jeden przypadek użycia żądanej funkcji.
Dan H

@ DanH Jest jeszcze gorzej z kodem strony trzeciej, ponieważ nie masz pojęcia, jakie szkody może to spowodować. Jeśli twoja biblioteka strony trzeciej nie jest wystarczająco solidna, aby wymagała zabicia, powinieneś wykonać jedną z następujących czynności: (1) poprosić autora o naprawienie problemu, (2) użyć czegoś innego. Jeśli naprawdę nie masz wyboru, umieszczenie tego kodu w odrębnym procesie powinno być bezpieczniejsze, ponieważ niektóre zasoby są współużytkowane tylko w jednym procesie.
Phil1970,

25

W Pythonie po prostu nie można zabić wątku bezpośrednio.

Jeśli tak naprawdę NIE potrzebujesz mieć wątku (!), Możesz zamiast pakietu wątków skorzystać z pakietu wieloprocesowego . Tutaj, aby zabić proces, możesz po prostu wywołać metodę:

yourProcess.terminate()  # kill the process!

Python zabije Twój proces (w systemie Unix za pośrednictwem sygnału SIGTERM, a w systemie Windows za pośrednictwem TerminateProcess()wywołania). Zachowaj ostrożność podczas korzystania z kolejki lub potoku! (może to uszkodzić dane w kolejce / potoku)

Zauważ, że multiprocessing.Eventi multiprocessing.Semaphoredziałają dokładnie w taki sam sposób jak threading.Eventi threading.Semaphoreodpowiednio. W rzeczywistości pierwsze są klonami latters.

Jeśli NAPRAWDĘ potrzebujesz użyć Wątku, nie ma sposobu, aby go zabić bezpośrednio. Możesz jednak użyć „wątku demona” . W rzeczywistości w Pythonie wątek może zostać oflagowany jako demon :

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

Program główny zakończy działanie, gdy nie pozostaną żadne żywe wątki inne niż demony. Innymi słowy, gdy główny wątek (który jest oczywiście wątkiem innym niż demon) zakończy działanie, program zakończy działanie, nawet jeśli nadal działają pewne wątki demona.

Zauważ, że konieczne jest ustawienie wątku, tak jak daemonprzed start()wywołaniem metody!

Oczywiście możesz i powinieneś używać daemonnawet z multiprocessing. Tutaj, gdy główny proces kończy działanie, próbuje zakończyć wszystkie swoje demoniczne procesy potomne.

Wreszcie, proszę zauważyć, że sys.exit()i os.kill()nie są wyborami.


14

Możesz zabić wątek, instalując śledzenie w wątku, który opuści wątek. Zobacz załączony link dla jednej możliwej implementacji.

Zabij wątek w Pythonie


Już to widziałem. To rozwiązanie opiera się na samobójczym sprawdzeniu flagi
Sudden Def

1
Jedna z niewielu odpowiedzi, które faktycznie DZIAŁA
Ponkadoodle,

5
Dwa problemy z tym rozwiązaniem: (a) zainstalowanie znacznika za pomocą sys.settrace () spowoduje spowolnienie działania wątku. Aż 10 razy wolniejszy, jeśli jest związany z obliczeniami. (b) nie wpłynie na twój wątek, gdy jest on w wywołaniu systemowym.
Matthias Urlichs

10

Jeśli są jawne wywołanie time.sleep()jako część gwintu (słownie odpytywania niektórych usług zewnętrznych), poprawa na metodzie Phillipe jest wykorzystanie limitu czasu w event„s wait()metody Gdziekolwieksleep()

Na przykład:

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()

Następnie, aby go uruchomić

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

Zaletą używania wait()zamiast sleep()ing i regularnego sprawdzania zdarzenia jest to, że możesz programować w dłuższych odstępach czasu, wątek jest zatrzymywany prawie natychmiast (gdy byś nie był sleep()), a moim zdaniem kod obsługi wyjścia jest znacznie prostszy .


3
dlaczego ten post został odrzucony? Co jest nie tak z tym postem? Wygląda dokładnie tak, jak potrzebuję ....
JDOaktown

9

Lepiej, jeśli nie zabijesz nici. Jednym ze sposobów może być wprowadzenie bloku „try” do cyklu wątku i zgłoszenie wyjątku, gdy chcesz zatrzymać wątek (na przykład break / return / ..., który zatrzymuje twój na / while / ...). Użyłem tego w mojej aplikacji i działa ...


8

Zdecydowanie możliwe jest zaimplementowanie Thread.stopmetody, jak pokazano w poniższym przykładowym kodzie:

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()

Wydaje się, że Thread3klasa uruchamia kod o około 33% szybciej niż Thread2klasa.


3
Jest to sprytny sposób na wstrzyknięcie sprawdzania self.__stopistnienia w wątku. Zauważ, że podobnie jak większość innych rozwiązań tutaj, tak naprawdę nie zakłóci wywołania blokującego, ponieważ funkcja śledzenia jest wywoływana tylko po wprowadzeniu nowego zasięgu lokalnego. Warto również zauważyć, że sys.settracetak naprawdę jest przeznaczony do wdrażania debuggerów, profili itp. I jako taki jest uważany za szczegół implementacji CPython i nie ma gwarancji, że będzie istniał w innych implementacjach Pythona.
dano

3
@dano: Jednym z największych problemów z Thread2klasą jest to, że uruchamia kod około dziesięć razy wolniej. Niektórzy ludzie mogą nadal uznać to za dopuszczalne.
Noctis Skytower

+1 w tej sprawie znacznie spowalnia wykonanie kodu. Sugeruję, aby autor tego rozwiązania zawarł tę informację w odpowiedzi.
Vishal,

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

t jest twoim Threadprzedmiotem.

Przeczytaj źródło Pythona ( Modules/threadmodule.ci Python/thread_pthread.h) możesz zobaczyć, że Thread.identjest to pthread_ttyp, więc możesz zrobić wszystko, co pthreadmożna zrobić w użyciu Pythona libpthread.


Jak to zrobić w systemie Windows?
iChux

12
Ty nie; nie w systemie Windows ani w systemie Linux. Powód: Wątek, o którym mowa, może zawierać GIL podczas wykonywania tej czynności (Python uwalnia GIL, gdy wywołujesz C). Jeśli tak, twój program natychmiast zakleszczy się. Nawet jeśli nie, w końcu: bloki nie zostaną wykonane itp., Więc jest to bardzo niebezpieczny pomysł.
Matthias Urlichs

6

Aby zabić wątek, można zastosować następujące obejście:

kill_threads = False

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

thread.start_new_thread(doSomething, ())

Można tego użyć nawet do zakończenia wątków, których kod jest zapisany w innym module z głównego wątku. Możemy zadeklarować zmienną globalną w tym module i użyć jej do zakończenia wątku / wątków spawnowanych w tym module.

Zwykle używam tego, aby zakończyć wszystkie wątki przy wyjściu z programu. To może nie być idealny sposób na zakończenie wątku / wątków, ale może pomóc.


Kciuki w górę. Prosty do zrozumienia.
alyssaeliyah

6

Spóźniłem się do tej gry, ale zmagam się z podobnym pytaniem, a poniższe wydaje się, że oba rozwiązują problem idealnie dla mnie ORAZ pozwalają mi na sprawdzenie podstawowego stanu wątku i oczyszczenie go po wyjściu demonizowanego wątku:

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()

Wydajność:

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

To doskonała odpowiedź, która powinna znaleźć się wyżej na liście
drootang

Dlaczego miałby podnosząc SystemExitna after_timeoutwątku nic do głównego wątku (który jest po prostu czeka na tego pierwszego zjazdu w tym przykładzie) zrobić?
Davis Herring

@ DavisHerring Nie jestem pewien, do czego zmierzasz. SystemExit zabija główny wątek. Jak myślisz, dlaczego POWINNO zrobić cokolwiek w głównym wątku? Bez tego wywołania program będzie po prostu czekał na wątek potomny. Możesz także ctrl + c lub użyć dowolnego innego sposobu, aby zabić główny wątek, ale to jest przykład.
slumtrimpet

@slumtrimpet: SystemExitma tylko dwie specjalne właściwości: nie generuje śledzenia (gdy dowolny wątek wychodzi przez rzucenie jednego), a jeśli główny wątek wychodzi przez rzucenie jednego, ustawia status wyjścia (niemniej czekając na inne wątki niebędące demonami do wyjścia).
Davis Herring

@DavisHerring Och, mam. Zupełnie się z Tobą zgadzam. Źle zapamiętałem to, co tutaj zrobiliśmy, i źle zrozumiałem twój komentarz.
slumtrimpet

4

Jedną rzeczą, którą chcę dodać, jest to, że jeśli czytasz oficjalną dokumentację w wątku lib Python , zaleca się unikanie używania wątków „demonicznych”, gdy nie chcesz, aby wątki nagle się kończyły, z flagą, o której wspomniał Paolo Rovelli .

Z oficjalnej dokumentacji:

Wątki demonów są nagle zatrzymywane przy wyłączaniu. Ich zasoby (takie jak otwarte pliki, transakcje w bazie danych itp.) Mogą nie zostać poprawnie zwolnione. Jeśli chcesz, aby twoje wątki zatrzymały się z gracją, uczyń je nie-demonicznymi i użyj odpowiedniego mechanizmu sygnalizacyjnego, takiego jak Zdarzenie.

Myślę, że tworzenie demonicznych wątków zależy od twojej aplikacji, ale ogólnie (i moim zdaniem) lepiej unikać ich zabijania lub demonizacji. W trybie wieloprocesowym możesz użyć is_alive()do sprawdzenia statusu procesu i „zakończyć”, aby je zakończyć (unikasz również problemów GIL). Ale czasem można znaleźć więcej problemów podczas wykonywania kodu w systemie Windows.

I zawsze pamiętaj, że jeśli masz „aktywne wątki”, interpreter Pythona będzie działał, aby na nie poczekać. (Z tego powodu demon może ci pomóc, jeśli nie ma znaczenia, nagle się kończy).


4
Nie rozumiem ostatniego akapitu.
tshepang

@Tshepang Oznacza to, że jeśli w twojej aplikacji są jakieś działające wątki nie-demoniczne, interpreter Pythona będzie działał, dopóki wszystkie wątki nie-demony zostaną wykonane . Jeśli nie obchodzi Cię, czy wątek (wątki) kończą się wraz z zakończeniem programu, uczynienie z nich demona może być przydatne.
Tom Myddeltyn

3

W tym celu zbudowano bibliotekę stopit . Chociaż niektóre z tych samych ostrzeżeń wymienionych tutaj nadal mają zastosowanie, przynajmniej ta biblioteka przedstawia regularną, powtarzalną technikę osiągania określonego celu.


1

Chociaż jest dość stary, może to być przydatne rozwiązanie dla niektórych:

Mały moduł rozszerzający funkcjonalność modułu wątków - pozwala jednemu wątkowi zgłaszać wyjątki w kontekście innego wątku. Podnosząc SystemExit, możesz w końcu zabić wątki pytona.

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)

Pozwala to „wątkowi zgłosić wyjątki w kontekście innego wątku” i w ten sposób zakończony wątek może obsłużyć zakończenie bez regularnego sprawdzania flagi przerwania.

Jednak zgodnie z jego oryginalnym źródłem istnieją pewne problemy z tym kodem.

  • Wyjątek zostanie zgłoszony tylko podczas wykonywania kodu bajtowego Pythona. Jeśli wątek wywołuje rodzimą / wbudowaną funkcję blokującą, wyjątek zostanie zgłoszony tylko wtedy, gdy wykonanie powróci do kodu python.
    • Istnieje również problem, jeśli wbudowana funkcja wewnętrznie wywołuje PyErr_Clear (), co skutecznie anulowałoby oczekujący wyjątek. Możesz spróbować go podnieść ponownie.
  • Tylko typy wyjątków można bezpiecznie zgłaszać. Wystąpienia wyjątków mogą powodować nieoczekiwane zachowanie, a zatem są ograniczone.
  • Poprosiłem o ujawnienie tej funkcji we wbudowanym module wątku, ale ponieważ ctypy stały się standardową biblioteką (od wersji 2.5), a ta
    funkcja prawdopodobnie nie będzie niezależna od implementacji, może pozostać
    niewidoczna.

0

Wygląda na to, że działa z pywin32 na Windows 7

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

0

Pieter Hintjens - jeden z założycieli projektu ØMQ - mówi, że używanie ØMQ i unikanie prymitywów synchronizacji, takich jak zamki, muteksy, zdarzenia itp., Jest najbezpieczniejszym i najbezpieczniejszym sposobem pisania programów wielowątkowych:

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

Obejmuje to powiedzenie wątkowi potomnemu, że powinien anulować swoją pracę. Można to zrobić, wyposażając wątek w gniazdo ØMQ i odpytując w tym gnieździe w celu wyświetlenia komunikatu z informacją, że należy go anulować.

Link zawiera także przykład wielowątkowego kodu Pythona z ØMQ.


0

Zakładając, że chcesz mieć wiele wątków tej samej funkcji, jest to IMHO najłatwiejsza implementacja, aby zatrzymać jeden przez 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

Zaletą jest to, że możesz mieć wiele takich samych i różnych funkcji i zatrzymać je wszystkie functionname.stop

Jeśli chcesz mieć tylko jeden wątek funkcji, nie musisz pamiętać identyfikatora. Przestań, jeśli doit.stop> 0.


0

Aby rozwinąć pomysł @ SCB (dokładnie tego potrzebowałem), aby utworzyć podklasę KillableThread z dostosowaną funkcją:

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()

Oczywiście, podobnie jak w przypadku @SBC, wątek nie czeka na uruchomienie nowej pętli w celu zatrzymania. W tym przykładzie zobaczysz komunikat „Killing Thread” wydrukowany zaraz po „About to kill thread” zamiast czekać jeszcze 4 sekundy na zakończenie wątku (ponieważ spaliśmy już 6 sekund).

Drugim argumentem konstruktora KillableThread jest twoja funkcja niestandardowa (tutaj print_msg). Argument Argumenty to argumenty, które będą używane podczas wywoływania funkcji ((„witaj świecie”)) tutaj.


0

Jak wspomniano w @ Kozyarchuk za odpowiedź , instalowanie ślad działa. Ponieważ ta odpowiedź nie zawierała kodu, oto działający, gotowy do użycia przykład:

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

Zatrzymuje się po wydrukowaniu 1i 2. 3nie jest drukowany.


-1

Możesz wykonać polecenie w procesie, a następnie zabić je przy użyciu identyfikatora procesu. Musiałem zsynchronizować dwa wątki, z których jeden sam się nie zwraca.

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

Rozpocznij wątek podrzędny za pomocą 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

To zła odpowiedź, zobacz komentarze

Oto jak to zrobić:

from threading import *

...

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

Daj mu kilka sekund, a następnie twój wątek powinien zostać zatrzymany. Sprawdź także thread._Thread__delete()metodę.

Polecam thread.quit()metodę dla wygody. Na przykład, jeśli masz gniazdo w swoim wątku, zaleciłbym utworzenie quit()metody w klasie gniazda-uchwyt, zakończenie gniazda, a następnie uruchomienie thread._Thread__stop()wewnątrz quit().


Musiałem użyć self._Thread__stop () wewnątrz mojego obiektu Threading.Thread, aby go zatrzymać. Nie rozumiem, dlaczego prosty self.join () jak ten przykład ( code.activestate.com/recipes/65448-thread-control-idiom ) nie działa.
harijay,

12
Przydałoby się więcej szczegółów na temat „To tak naprawdę nie zatrzymuje wątku”.
2371

19
Zasadniczo wywołanie metody _Thread__stop nie ma żadnego wpływu poza poinformowaniem Pythona, że ​​wątek został zatrzymany. Może faktycznie nadal działać. Przykład: patrz gist.github.com/2787191 .
Bluehorn

35
To jest po prostu źle. _Thread__stop()jedynie zaznacza wątek jako zatrzymany , tak naprawdę nie zatrzymuje wątku! Nigdy tego nie rób. Przeczytaj .
dotancohen

-2

Jeśli naprawdę potrzebujesz możliwości zabicia podzadania, użyj alternatywnej implementacji. multiprocessingi geventoba wspierają bezkrytycznie zabijanie „wątku”.

Wątek Pythona nie obsługuje anulowania. Nawet nie próbuj. Twój kod najprawdopodobniej zakleszczy się, uszkodzi lub wyciek pamięci lub wywoła inne niezamierzone „interesujące” trudne do debugowania efekty, które zdarzają się rzadko i nie są deterministyczne.


2
… I tak, wiem, że oba nie są ściśle „wątkowe”, ale oba działają, jeśli twój kod pasuje (lub można go dopasować) do jego modelu.
Matthias Urlichs,
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.