有没有办法终止一个线程?

999 投票
31 回答
1266987 浏览
提问于 2025-04-11 19:21

有没有办法在不设置或检查任何标志、信号量等的情况下,直接结束一个正在运行的线程呢?

31 个回答

141

没有官方的接口可以做到这一点。

你需要使用平台提供的接口来结束线程,比如 pthread_kill 或 TerminateThread。你可以通过 pythonwin 或 ctypes 来访问这些接口。

要注意,这样做本身是不安全的。如果你强行结束一个线程,可能会导致一些无法回收的垃圾(比如那些变成垃圾的局部变量),还可能会造成死锁,特别是当被结束的线程在被杀掉时持有全局解释器锁(GIL)。

184

一个 multiprocessing.Process 可以通过 p.terminate() 来结束。

在我想要结束一个线程,但又不想使用标志、锁、信号、信号量、事件等复杂东西的时候,我会把线程升级为完整的进程。对于只使用几个线程的代码来说,这样的开销并不是很大。

比如,这种方法很方便,可以轻松结束那些执行阻塞输入输出的辅助“线程”。

转换过程很简单:在相关的代码中,把所有的 threading.Thread 替换为 multiprocessing.Process,把所有的 queue.Queue 替换为 multiprocessing.Queue,然后在想要结束子进程 p 的父进程中添加必要的 p.terminate() 调用。

可以查看 Python 的 multiprocessing 文档 了解更多信息。

示例:

import multiprocessing
proc = multiprocessing.Process(target=your_proc_function, args=())
proc.start()
# Terminate the process
proc.terminate()  # sends a SIGTERM
853

在Python以及其他编程语言中,突然终止一个线程通常不是个好主意。想想以下几种情况:

  • 线程正在使用一个重要的资源,这个资源需要正确关闭。
  • 这个线程还创建了其他几个线程,这些线程也需要被终止。

如果你能控制线程的管理,处理这个问题的好方法是设置一个退出请求标志,让每个线程定期检查一下,看看是否该退出了。

举个例子:

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

在这段代码中,当你想让线程退出时,应该调用 stop() 方法,并使用 join() 等待线程正常退出。线程应该定期检查这个停止标志。

不过,有时候你确实需要强制终止一个线程。比如,当你在使用一个外部库,而这个库的调用时间很长,你想要中断它。

下面的代码可以在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 an 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.is_alive(): # Note: self.isAlive() on older version of Python
            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 raise_exc(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.raise_exc( SomeException )
            while t.isAlive():
                time.sleep( 0.1 )
                t.raise_exc( 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 exception in the context of the
        thread represented by this instance.
        """
        _async_raise( self._get_my_tid(), exctype )

(基于Tomer Filiba的可杀线程。关于 PyThreadState_SetAsyncExc 返回值的引用似乎来自于一个旧版本的Python。)

正如文档中提到的,这并不是万能的解决方案,因为如果线程在Python解释器外部忙碌,它将无法捕捉到中断。

使用这段代码的一个好方法是让线程捕捉一个特定的异常并进行清理。这样,你可以中断一个任务,同时确保有适当的清理工作。

撰写回答