Maison  >  Article  >  développement back-end  >  Ne forcez pas la suppression des threads Python

Ne forcez pas la suppression des threads Python

高洛峰
高洛峰original
2017-02-28 09:06:341605parcourir

Avant-propos :

N'essayez pas de tuer un thread python par la force, c'est déraisonnable en termes de conception de service. Le multithreading est utilisé pour la concurrence collaborative des tâches. Si vous utilisez la force pour tuer des threads, il existe un risque élevé de bogues inattendus. N'oubliez pas que la ressource de verrouillage ne sera pas libérée car le thread se termine !

Nous pouvons donner deux exemples courants :

1. Le thread A a obtenu le verrou car il a été tué de force et n'a pas réussi à libérer la ressource de verrouillage à temps avec release() , alors tous les threads seront bloqué dans l’acquisition de ressources, ce qui est un scénario d’impasse typique.

2. Dans un scénario production-consommateur courant, le consommateur obtient les tâches de la file d'attente des tâches, mais ne remet pas la tâche en cours dans la file d'attente après avoir été tué, ce qui entraîne une perte de données.

Voici les façons de terminer les threads en Java et Python :

Java dispose de trois méthodes pour terminer les threads :

1. indicateur de sortie pour que le thread se termine normalement, c'est-à-dire que le thread se termine lorsque la méthode run est terminée.
2. Utilisez la méthode stop pour terminer de force le thread (non recommandé, car l'arrêt est la même chose que la suspension et la reprise, et des résultats imprévisibles peuvent survenir).
3. Utilisez la méthode d'interruption pour interrompre le fil.

Python peut avoir deux méthodes :

1. Marque de sortie
2 Utilisez des ctypes pour tuer le fil de force

Non. important Dans un environnement Python ou Java, le moyen idéal pour arrêter et quitter un thread est de laisser le thread se suicider. Ce qu'on appelle le suicide du thread signifie que vous lui donnez un indicateur et qu'il quitte le thread.

Ci-dessous, nous utiliserons diverses méthodes pour tester la situation anormale d'arrêt du thread python. Nous examinons tous les threads d'exécution d'un processus. Le processus utilise des ressources de contrôle et le thread est utilisé comme unité de planification. Pour être planifié pour l'exécution, un processus doit avoir un thread par défaut. le processus.

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

Après avoir obtenu tous les threads du processus, nous savons grâce à strace que 31450 est l'ID de thread qui doit être tué lorsque nous tuons, il apparaîtra Une situation où l'ensemble du processus se bloque. Dans un environnement multithread, le signal généré est transmis à l'ensemble du processus. De manière générale, tous les threads ont la possibilité de recevoir ce signal. Le processus exécute la fonction de traitement du signal dans le contexte du thread qui reçoit le signal. Difficile de le savoir. En d’autres termes, le signal sera envoyé aléatoirement à un thread du processus.

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

Le problème ci-dessus est en fait cohérent avec la description de pthread. Lorsque nous ajoutons la fonction de traitement du signal au code python, la fonction de rappel peut empêcher l'ensemble du processus de se terminer. Le problème survient alors. La fonction signal ne peut pas identifier le thread que vous souhaitez tuer. certain fil. Bien que vous envoyiez le signal à l'ID de thread 31450, l'accepteur de signal est l'un des processus auquel il appartient. De plus, les paramètres transmis à la fonction de traitement du signal sont uniquement le numéro de signal et la pile de signaux, qui sont facultatifs.

Après l'ajout du traitement du signal, le processus ne se terminera pas

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)

Si vous souhaitez tuer un thread à partir d'un externe notification , vous pouvez alors créer et utiliser des services rpc, ou communiquer par d'autres moyens, mais les signaux ne le peuvent pas, car ils ne peuvent pas transmettre plus d'informations.

Les threads Python ne sont pas simulés, ce sont de vrais threads du noyau. Le noyau appelle la méthode pthread, mais la couche supérieure de Python ne fournit pas de méthode pour fermer le thread, nous devons donc le comprendre nous-mêmes. Il est fortement recommandé d'utiliser des méthodes d'événement ou de bit d'indicateur personnalisé. Si vous devez tuer le thread de force, vous pouvez utiliser la méthode python ctypes PyThreadState SetAsyncExc pour forcer la sortie, ce qui n'aura aucun impact sur le service Python en cours d'exécution.

Le principe de mise en œuvre de cette fonction est relativement simple. En fait, il s'agit de définir un indicateur dans la machine virtuelle python, puis la machine virtuelle exécutera une exception pour annuler le thread virtuel. la machine vous aidera à faire un essai du cache. N'oubliez pas de ne pas tuer un thread en Python en externe. Bien que vous puissiez trouver l'ID du thread via les ctypes, le tuer directement tuera l'ensemble du processus.

Le code suivant est un exemple d'utilisation de ctypes pour tuer un fil de discussion. Il n'est pas recommandé car il est trop grossier.

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

Jetons un bref coup d'œil au code source de PyThreadState. Bref, le mode exception du thread est déclenché. Ceux qui sont intéressés peuvent lire le design de python pystate.c et le partager avec quelques vidéos sur YouTube.


 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;
}

Le pthread posix natif peut utiliser ptread_cancel(tid) pour terminer le fil enfant dans le fil principal. Cependant, la bibliothèque de threads de Python ne prend pas en charge cela. La raison est que nous ne devons pas forcer la fin d'un thread. Cela entraînerait de nombreux dangers cachés et le thread devrait être autorisé à se terminer lui-même. Par conséquent, en Python, la méthode recommandée consiste à parcourir un sous-thread pour déterminer un indicateur, à modifier l'indicateur dans le thread principal et à se terminer lorsque le sous-thread lit le changement d'indicateur.

Semblable à cette logique :

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

Un bref résumé, bien que nous puissions utiliser des pystats dans les ctypes pour contrôler les threads, cette méthode d'interruption brutale des threads est déraisonnable. S'il vous plaît, utilisez le mode suicide ! Que se passe-t-il si votre thread bloque io et ne peut pas déterminer l'événement ? Votre programme doit être optimisé. Au moins, il doit avoir un délai d'attente actif au niveau de la couche IO du réseau pour éviter d'être bloqué à tout moment.

Pour plus d'articles sur la non-utilisation de méthodes forcées pour tuer les threads python, veuillez faire attention au site Web PHP chinois !

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn