Maison  >  Article  >  développement back-end  >  Introduction aux méthodes de terminaison non forcée des threads Python

Introduction aux méthodes de terminaison non forcée des threads Python

Y2J
Y2Joriginal
2017-05-06 14:51:231666parcourir

Cet article partage avec vous certaines expériences et leçons apprises dans la suppression forcée des threads Python. Si vous utilisez la force pour tuer des threads, il y a de fortes chances que des bugs inattendus se produisent. N'oubliez pas que la ressource de verrouillage ne sera pas libérée car le thread se termine !

Avant-propos :

N'essayez pas de tuer un thread Python en utilisant des méthodes forcées, cela 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 ferme !

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 que nous devons tuer. Lorsque nous tuons, tout le processus plante. Dans un environnement multithread, le signal généré est délivré à 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. signal. Il est difficile de savoir quel thread est en cours d'exécution. 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. , il ne peut pas tuer un thread avec précision. 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 ne sont que le nombre de signaux et le. pile de signaux, qui sont facultatives.

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 supprimer un fil de discussion à partir d'une notification externe, vous pouvez créer et utiliser le service rpc, ou communiquer par d'autres moyens, 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 la méthode 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 python en cours d'exécution. service.

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 à essayer le 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 thread. Ce 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 simplement un coup d'œil au code source de PyThreadState. Bref, l'exception qui déclenche le modèle de thread. 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;
}

原生posix pthread 可以使用 ptread_cancel(tid) 在主线程中结束子线程。但是 Python 的线程库不支持这样做,理由是我们不应该强制地结束一个线程,这样会带来很多隐患,应该让该线程自己结束自己。所以在 Python 中,推荐的方法是在子线程中循环判断一个标志位,在主线程中改变该标志位,子线程读到标志位改变,就结束自己。

类似这个逻辑:

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

简单的总结,虽然我们可以用ctypes里的pystats来控制线程,但这种粗暴中断线程的方法是不合理的。 请选用 自杀模式 !如果你的线程正在发生io阻塞,而不能判断事件怎么办? 你的程序需要做优化了,最少在网络io层需要有主动的timeout,避免一直的阻塞下去。

【相关推荐】

1. Python免费视频教程

2. Python基础入门教程

3. Python面向对象视频教程

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en 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