Heim >Backend-Entwicklung >Python-Tutorial >Erzwingen Sie nicht das Beenden von Python-Threads

Erzwingen Sie nicht das Beenden von Python-Threads

高洛峰
高洛峰Original
2017-02-28 09:06:341713Durchsuche

Vorwort:

Versuchen Sie nicht, einen Python-Thread mit Gewalt zu beenden. Dies ist im Hinblick auf das Service-Design unvernünftig. Multithreading wird für die kollaborative Parallelität von Aufgaben verwendet. Wenn Sie Threads gewaltsam beenden, besteht eine hohe Wahrscheinlichkeit, dass unerwartete Fehler auftreten. Bitte beachten Sie, dass die Sperrressource nicht freigegeben wird, da der Thread beendet wird!

Wir können zwei gängige Beispiele nennen:

1. Thread A hat die Sperre erhalten, weil er gewaltsam beendet wurde und die Sperrressource nicht rechtzeitig mit release() freigegeben wurde. Dann werden alle Threads die Sperre erhalten beim Erwerb von Ressourcen blockiert, was ein typisches Deadlock-Szenario darstellt.

2. In einem üblichen Produktions-Konsumenten-Szenario ruft der Konsument Aufgaben aus der Aufgabenwarteschlange ab, wirft die laufende Aufgabe jedoch nicht zurück in die Warteschlange, nachdem sie beendet wurde, was zu Datenverlust führt.

Es gibt folgende Möglichkeiten, Threads in Java und Python zu beenden:

Java verfügt über drei Methoden, um Threads zu beenden:

1 Exit-Flag, um den Thread normal zu beenden, d. h. der Thread wird beendet, wenn die Ausführungsmethode abgeschlossen ist.
2. Verwenden Sie die Stop-Methode, um den Thread zwangsweise zu beenden (nicht empfohlen, da Stop dasselbe ist wie Anhalten und Fortsetzen und unvorhersehbare Ergebnisse auftreten können).
3. Verwenden Sie die Interrupt-Methode, um den Thread zu unterbrechen.

Python kann zwei Methoden haben:

1. Exit-Markierung
2. Verwenden Sie ctypes, um den Thread gewaltsam zu beenden

Nein Angelegenheit In einer Python- oder Java-Umgebung besteht die ideale Möglichkeit, einen Thread zu stoppen und zu beenden, darin, den Thread Selbstmord begehen zu lassen. Der sogenannte Thread-Selbstmord bedeutet, dass Sie ihm ein Flag geben und er den Thread verlässt.

Im Folgenden werden wir verschiedene Methoden verwenden, um die abnormale Situation beim Stoppen des Python-Threads zu testen. Wir betrachten alle Ausführungsthreads eines Prozesses. Der Prozess verwendet Steuerressourcen und der Thread wird als Planungseinheit verwendet. Um für die Ausführung geplant zu werden, muss ein Thread vorhanden sein der Prozess.

ps -mp 31449 -o THREAD,tid
 
USER   %CPU PRI SCNT WCHAN USER SYSTEM  TID
root   0.0  -  - -     -   -   -
root   0.0 19  - poll_s  -   - 31449
root   0.0 19  - poll_s  -   - 31450

Nachdem wir alle Threads des Prozesses erhalten haben, wissen wir durch Strace, dass 31450 die Thread-ID ist, die getötet werden muss. Es erscheint eine Situation, in der der gesamte Prozess abstürzt. In einer Multithread-Umgebung wird das generierte Signal an den gesamten Prozess weitergeleitet. Im Allgemeinen haben alle Threads die Möglichkeit, dieses Signal zu empfangen. Der Prozess führt die Signalverarbeitungsfunktion in dem Thread-Kontext aus, der das Signal empfängt es. Schwer zu wissen. Mit anderen Worten: Das Signal wird zufällig an einen Thread des Prozesses gesendet.

strace -p <span style="font-size:14px;line-height:21px;">31450</span> Process <span style="font-size:14px;line-height:21px;">31450</span> attached - interrupt to quit
select(0, NULL, NULL, NULL, {0, 320326}) = 0 (Timeout)
select(0, NULL, NULL, NULL, {1, 0})   = 0 (Timeout)
select(0, NULL, NULL, NULL, {1, 0})   = 0 (Timeout)
select(0, NULL, NULL, NULL, {1, 0})   = ? ERESTARTNOHAND (To be restarted)
--- SIGTERM (Terminated) @ 0 (0) ---
Process <span style="font-size:14px;line-height:21px;">31450</span> detached

Das obige Problem stimmt tatsächlich mit der Beschreibung von pthread überein. Wenn wir die Signalverarbeitungsfunktion zum Python-Code hinzufügen, kann die Rückruffunktion verhindern, dass der gesamte Prozess beendet wird. Mit anderen Worten: Die Signalfunktion kann nicht genau identifizieren bestimmter Thread. Obwohl Sie das Signal an die Thread-ID 31450 senden, ist der Signalakzeptor einer der Prozesse, zu denen es gehört. Darüber hinaus sind die an die Signalverarbeitungsfunktion übergebenen Parameter nur die Signalnummer und der Signalstapel, die optional sind.

Nach dem Hinzufügen der Signalverarbeitung wird der Prozess nicht beendet

select(0, NULL, NULL, NULL, {1, 0})   = 0 (Timeout)
select(0, NULL, NULL, NULL, {1, 0})   = ? ERESTARTNOHAND (To be restarted)
--- SIGTERM (Terminated) @ 0 (0) ---
rt_sigreturn(0xffffffff)        = -1 EINTR (Interrupted system call)
select(0, NULL, NULL, NULL, {1, 0})   = 0 (Timeout)
select(0, NULL, NULL, NULL, {1, 0})   = 0 (Timeout)

Wenn Sie einen Thread von extern beenden möchten Benachrichtigung , dann können Sie RPC-Dienste erstellen und verwenden oder auf andere Weise kommunizieren, Signalsignale jedoch nicht, da sie keine weiteren Informationen übertragen können.

Python-Threads werden nicht simuliert, es handelt sich um echte Kernel-Threads. Der Kernel ruft die pthread-Methode auf, aber die obere Schicht von Python bietet keine Methode zum Schließen des Threads, daher müssen wir sie selbst verstehen. Es wird dringend empfohlen, Ereignis- oder benutzerdefinierte Flag-Bit-Methoden zu verwenden. Wenn Sie den Thread zwangsweise beenden müssen, können Sie das Beenden mit der Python-ctypes-Methode „PyThreadState SetAsyncExc“ erzwingen, was keine Auswirkungen auf den laufenden Python-Dienst hat.

Das Implementierungsprinzip dieser Funktion ist relativ einfach. Tatsächlich besteht es darin, ein Flag in der virtuellen Python-Maschine zu setzen, und dann führt die virtuelle Maschine eine Ausnahme aus, um den Thread abzubrechen Die Maschine hilft Ihnen beim Erstellen eines Test-Cache. Denken Sie daran, einen Thread in Python nicht extern zu beenden. Obwohl Sie die Thread-ID über ctypes finden können, wird durch das direkte Beenden der gesamte Prozess beendet.

Der folgende Code ist ein Beispiel für die Verwendung von ctypes zum Beenden eines Threads. Er wird nicht empfohlen, da er zu unhöflich ist.

import ctypes
 
def terminate_thread(thread):
  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:
    ctypes.pythonapi.PyThreadState_SetAsyncExc(thread.ident, None)
    raise SystemError("PyThreadState_SetAsyncExc failed")

Werfen wir einen kurzen Blick auf den PyThreadState-Quellcode. Kurz gesagt, der Ausnahmemodus des Threads wird ausgelöst. Interessierte können das Design von Python pystate.c lesen und mit einigen Videos auf YouTube teilen.

 int
PyThreadState_SetAsyncExc(long id, PyObject *exc) {
  PyInterpreterState *interp = GET_INTERP_STATE();
  ...
  HEAD_LOCK();
  for (p = interp->tstate_head; p != NULL; p = p->next) {
    if (p->thread_id == id) {
      从链表里找到线程的id,避免死锁,我们需要释放head_mutex。
      PyObject *old_exc = p->async_exc;
      Py_XINCREF(exc); #增加该对象的引用数
      p->async_exc = exc; # 更为exc模式
      HEAD_UNLOCK();
      Py_XDECREF(old_exc); # 因为要取消,当然也就递减引用
      ...
      return 1; #销毁线程成功
    }
  }
  HEAD_UNLOCK();
  return 0;
}

Nativer Posix-Pthread kann ptread_cancel(tid) verwenden, um den untergeordneten Thread im Hauptthread zu beenden. Die Thread-Bibliothek von Python unterstützt dies jedoch nicht. Der Grund dafür ist, dass wir einen Thread nicht gewaltsam beenden sollten. Dies birgt viele versteckte Gefahren und der Thread sollte sich selbst beenden dürfen. Daher besteht in Python die empfohlene Methode darin, einen Sub-Thread zu durchlaufen, um ein Flag zu ermitteln, das Flag im Haupt-Thread zu ändern und sich selbst zu beenden, wenn der Sub-Thread die Flag-Änderung liest.

Ähnlich dieser Logik:

def consumer_threading():
 t1_stop= threading.Event()
 t1 = threading.Thread(target=thread1, args=(1, t1_stop))
 
 t2_stop = threading.Event()
 t2 = threading.Thread(target=thread2, args=(2, t2_stop))
 
 time.sleep(duration)
 #stop the thread2
 t2_stop.set()
 
def thread1(arg1, stop_event):
 while(not stop_event.is_set()):
   #similar to time.sleep()
   stop_event.wait(time)
   pass
 
 
def thread2(arg1, stop_event):
 while(not stop_event.is_set()):
   stop_event.wait(time)
   pass

Eine kurze Zusammenfassung: Obwohl wir Pystats in Ctypes verwenden können, um Threads zu steuern, ist diese Methode der groben Unterbrechung von Threads unvernünftig. Bitte verwenden Sie den Selbstmordmodus! Was passiert, wenn Ihr Thread io blockiert und das Ereignis nicht ermitteln kann? Ihr Programm muss mindestens über ein aktives Timeout auf der Netzwerk-E/A-Ebene verfügen, um eine kontinuierliche Blockierung zu vermeiden.

Weitere verwandte Artikel zum Thema, keine erzwungenen Methoden zum Beenden von Python-Threads zu verwenden, finden Sie auf der chinesischen PHP-Website!

Stellungnahme:
Der Inhalt dieses Artikels wird freiwillig von Internetnutzern beigesteuert und das Urheberrecht liegt beim ursprünglichen Autor. Diese Website übernimmt keine entsprechende rechtliche Verantwortung. Wenn Sie Inhalte finden, bei denen der Verdacht eines Plagiats oder einer Rechtsverletzung besteht, wenden Sie sich bitte an admin@php.cn