Czy można zakończyć działający wątek bez ustawiania / sprawdzania flag / semaforów / itp.?
źródło
Czy można zakończyć działający wątek bez ustawiania / sprawdzania flag / semaforów / itp.?
Zasadniczo nagłe zabicie wątku w Pythonie i dowolnym języku jest złym wzorcem. Pomyśl o następujących przypadkach:
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_SetAsyncExc
wydaje 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.
SO_REUSEADDR
opcji gniazda, aby uniknąćAddress already in use
błędu.None
zamiast0
wres != 1
przypadku i musiałem to wywołaćctypes.c_long(tid)
i przekazać do dowolnej funkcji ctyp, a nie bezpośrednio do TID.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.
źródło
multiprocessing.Process
puszkap.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.Thread
zmultiprocessing.Process
a wszystkoqueue.Queue
zmultiprocessing.Queue
i dodać wymagane nawoływaniap.terminate()
do procesu macierzystego, który chce zabić swoje dzieckop
Zobacz dokumentację Pythona dla
multiprocessing
.źródło
multiprocessing
jest 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 alogging.log
), może nie być dobrym pomysłemmultiprocessing
.multiprocessing
argumenty 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.multiprocessing
logowanie jest trudnym biznesem. Musisz użyćQueueHandler
(zobacz ten samouczek ). Nauczyłem się tego na własnej skórze.Jeśli próbujesz zakończyć cały program, możesz ustawić wątek jako „demon”. zobacz Thread.daemon
źródło
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()
.)Wymiana
print()
zpr()
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)
źródło
pr()
funkcja?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.
źródło
KeyboardInterrupt
aby miały szansę oczyścić. Jeśli nadal wiszą po tym, toSystemExit
jest odpowiednie, lub po prostu zabij proces z terminala.pthread_cleanup_push()/_pop()
poprawnej implementacji wymagałoby dużo pracy i znacznie spowolniłoby interpretera.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.
źródło
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ę:
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.Event
imultiprocessing.Semaphore
działają dokładnie w taki sam sposób jakthreading.Event
ithreading.Semaphore
odpowiednio. 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 :
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
daemon
przedstart()
wywołaniem metody!Oczywiście możesz i powinieneś używać
daemon
nawet zmultiprocessing
. 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()
ios.kill()
nie są wyborami.źródło
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
źródło
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 wevent
„swait()
metody Gdziekolwieksleep()
Na przykład:
Następnie, aby go uruchomić
Zaletą używania
wait()
zamiastsleep()
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 .źródło
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 ...
źródło
Zdecydowanie możliwe jest zaimplementowanie
Thread.stop
metody, jak pokazano w poniższym przykładowym kodzie:Wydaje się, że
Thread3
klasa uruchamia kod o około 33% szybciej niżThread2
klasa.źródło
self.__stop
istnienia 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ć, żesys.settrace
tak 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.Thread2
klasą jest to, że uruchamia kod około dziesięć razy wolniej. Niektórzy ludzie mogą nadal uznać to za dopuszczalne.t jest twoim
Thread
przedmiotem.Przeczytaj źródło Pythona (
Modules/threadmodule.c
iPython/thread_pthread.h
) możesz zobaczyć, żeThread.ident
jest topthread_t
typ, więc możesz zrobić wszystko, copthread
można zrobić w użyciu Pythonalibpthread
.źródło
Aby zabić wątek, można zastosować następujące obejście:
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.
źródło
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:
Wydajność:
źródło
SystemExit
naafter_timeout
wątku nic do głównego wątku (który jest po prostu czeka na tego pierwszego zjazdu w tym przykładzie) zrobić?SystemExit
ma 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).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:
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).
źródło
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.
źródło
Chociaż jest dość stary, może to być przydatne rozwiązanie dla niektórych:
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.
źródło
Wygląda na to, że działa z pywin32 na Windows 7
źródło
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.
źródło
Zakładając, że chcesz mieć wiele wątków tej samej funkcji, jest to IMHO najłatwiejsza implementacja, aby zatrzymać jeden przez id:
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.źródło
Aby rozwinąć pomysł @ SCB (dokładnie tego potrzebowałem), aby utworzyć podklasę KillableThread z dostosowaną funkcją:
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.
źródło
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:
Zatrzymuje się po wydrukowaniu
1
i2
.3
nie jest drukowany.źródło
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.
źródło
Rozpocznij wątek podrzędny za pomocą setDaemon (True).
źródło
Oto jak to zrobić:
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 utworzeniequit()
metody w klasie gniazda-uchwyt, zakończenie gniazda, a następnie uruchomieniethread._Thread__stop()
wewnątrzquit()
.źródło
_Thread__stop()
jedynie zaznacza wątek jako zatrzymany , tak naprawdę nie zatrzymuje wątku! Nigdy tego nie rób. Przeczytaj .Jeśli naprawdę potrzebujesz możliwości zabicia podzadania, użyj alternatywnej implementacji.
multiprocessing
igevent
oba 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.
źródło