Home  >  Article  >  Backend Development  >  Introduction to methods of non-forced termination of python threads

Introduction to methods of non-forced termination of python threads

Y2J
Y2JOriginal
2017-05-06 14:51:231663browse

This article shares with you some experience and lessons learned in forcibly killing python threads. If you use force to kill threads, there is a high chance that unexpected bugs will appear. Please remember that the lock resource will not be released because the thread exits!

Foreword:

Don’t try to kill a python thread by force. This is unreasonable in terms of service design. Multi-threading is used for collaborative concurrency of tasks. If you use force to kill threads, there is a high chance that unexpected bugs will occur. Please remember that the lock resource will not be released because the thread exits!

We can cite two common examples:

1. Thread A got the lock because it was forcibly killed and failed to release the lock resource in time with release() , then all threads will be blocked in acquiring resources, which is a typical deadlock scenario.

2. In a common production-consumer scenario, the consumer obtains tasks from the task queue, but does not throw the ongoing task back into the queue after being killed, which results in data loss.

The following are methods for terminating threads in java and python:

Java has three methods to terminate threads:

1. Use exit flag, so that the thread exits normally, that is, the thread terminates when the run method is completed.
2. Use the stop method to forcefully terminate the thread (not recommended, because stop is the same as suspend and resume, and unpredictable results may occur).
3. Use the interrupt method to interrupt the thread.

Python can have two methods:

1. Exit mark
2. Use ctypes to forcefully kill the thread

No matter In a Python or Java environment, the ideal way to stop and exit a thread is to let the thread commit suicide. The so-called thread suicide means that you give it a flag and it exits the thread.

Below we will use a variety of methods to test the abnormal situation of stopping the python thread. We look at all the execution threads of a process. The process uses control resources, and the thread is used as a scheduling unit. To be scheduled for execution, a process must have a thread. The default thread is the same as the pid of the process.

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

After obtaining all the threads of the process, we know through strace that 31450 is the thread ID that we need to kill. When we kill, the entire process will crash. In a multi-threaded environment, the generated signal is passed to the entire process. Generally speaking, all threads have the opportunity to receive this signal. The process executes signal processing function in the thread context that receives the signal. It is difficult to know which thread is executing. In other words, the signal will be sent to a thread of the process at random.

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

The above problem is actually consistent with the description of pthread. When we add the signal signal processing function to the python code, Callback function can prevent the entire process from exiting. Then the problem comes. The signal function cannot identify which thread you want to kill. In other words, it cannot Kill a thread accurately. Although you send the signal to the 31450 thread ID, the signal acceptor is any one of the process to which it belongs. In addition, the parameters passed to the signal processing function are only the number of signals and the signal stack, which are optional.

After adding signal processing, the process will not exit

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)

If you want to kill a thread from the outside, you can build an rpc service or communicate in other ways. The signal signal does not Yes, because there is no way to pass on more information.

Python threads are not simulated, they are real kernel threads. The kernel calls the pthread method, but the upper layer of Python does not provide a method to close the thread, so we need to control it ourselves. It is strongly recommended to use event or custom flag bit methods. If you must forcefully kill the thread, you can use the python ctypes PyThreadState SetAsyncExc method to force exit, which will have no impact on the running python service.

The implementation principle of this function is relatively simple. In fact, it is to make a mark in the python virtual machine, and then the virtual machine runs an exception to cancel the thread. The virtual machine will help you make a try cache. Remember not to kill a certain thread of Python externally. Although you can find the thread ID through ctypes, killing it directly will kill the entire process.

The following code is an example of using ctypes to kill a thread. It is not recommended because it is too rude.

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

Let's simply look at the PyThreadState source code. In short, the exception mode of the thread is triggered. Those who are interested can read the design of python pystate.c and share it with some videos on 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面向对象视频教程

The above is the detailed content of Introduction to methods of non-forced termination of python threads. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn