有没有办法杀死一个线程?

Sud*_*Def 710 python multithreading kill terminate

是否可以在不设置/检查任何标志/信号量/等的情况下终止正在运行的线程?

Phi*_*e F 637

在Python和任何语言中突然杀死一个线程通常是一种糟糕的模式.想想以下情况:

  • 线程正在持有必须正确关闭的关键资源
  • 线程已经创建了几个必须被杀死的其他线程.

如果你负担得起它(如果你正在管理自己的线程),处理这个问题的好方法是有一个exit_request标志,每个线程定期检查它是否有时间退出.

例如:

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()
Run Code Online (Sandbox Code Playgroud)

在此代码中,您应该在线程上调用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 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.isAlive():
            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 raiseExc(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.raiseExc( SomeException )
            while t.isAlive():
                time.sleep( 0.1 )
                t.raiseExc( 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 excpetion in the context of the
        thread represented by this instance.
        """
        _async_raise( self._get_my_tid(), exctype )
Run Code Online (Sandbox Code Playgroud)

(基于Tomer Filiba的Killable Threads.关于返回值的引用stop()似乎来自旧版本的Python.)

如文档中所述,这不是一个神奇的子弹,因为如果线程在Python解释器之外繁忙,它将不会捕获中断.

此代码的良好使用模式是让线程捕获特定异常并执行清理.这样,您可以中断任务并仍然进行适当的清理.

  • @ Bluebird75:此外,我不确定我是否认为线程不应该被突然杀死"因为线程可能持有必须正确关闭的关键资源":主程序和主程序也是如此可以被用户突然杀死(例如在Unix中使用Ctrl-C) - 在这种情况下,他们尽可能地尽可能地处理这种可能性.因此,我没有看到线程的特殊之处,以及为什么它们不应该像主程序那样得到相同的处理(即它们可以突然被杀死).你能详细说明一下吗? (66认同)
  • 值得一提的是,_stop已经在Python 3线程库中占用了.因此,可能使用不同的变量,否则您将收到错误. (21认同)
  • @EOL:另一方面,如果线程拥有的所有资源都是本地资源(打开文件,套接字),那么Linux在进程清理方面相当不错,而且不会泄漏.虽然我使用套接字创建了一个服务器,但是如果我使用Ctrl-C进行残酷的中断,我可以不再启动程序,因为它无法绑定套接字.我需要等5分钟.正确的解决方案是捕获Ctrl-C并清除套接字断开连接. (15认同)
  • 注意这个答案:至少对我来说(py2.6),我必须为`res!= 1`情况传递`None`而不是'0`,我不得不调用`ctypes.c_long(tid)`并将其传递给任何ctypes函数而不是直接传递给tid. (12认同)
  • @Bluebird75:顺便说一下.您可以使用`SO_REUSEADDR`套接字选项来避免"Address in in use"错误. (8认同)
  • 这段代码不太有效.您需要将tid转换为long,如:ctypes.pythonapi.PyThreadState_SetAsyncExc(ctypes.c_long(tid),None) (3认同)
  • @EOL:在我记忆中,使用线程没有特定的优势.事件确实存在. (3认同)
  • 我不同意.当你说"杀死一个线程"时 - 你的意思是"向线程发送信号".(这可能是神奇的#9,或者最好是另一个).有很多时候这是有道理的.例如,如果线程处于某种阻塞系统调用中,则需要*能够向其发送信号以将其弹出,即使与"优雅"机制一起退出[轮询]线程也是如此. (3认同)
  • 我喜欢'StoppableThread`方法.我添加了一个类`ThreadKilled(KeyboardInterrupt):pass`并添加了`def sleep(self,time):self._stop.wait(time); 如果self.stopped():提高ThreadKilled`.这样,只要线程用`self.sleep(t)`替换对`time.sleep(t)`的调用,线程就可以从长时间的睡眠中被杀死.从那里开始,我继续使用`Queue.Queue`代替`threading.Event`,因为它提供相同的基本功能(包括`sleep(t)`),但也让线程成为消费者. (3认同)
  • @ Bluebird75:在第一个例子中,为什么要使用`threading.Event`而不是可以从线程内部检查的简单布尔属性`stopped`? (2认同)
  • @Messa,SO_REUSEADDR并不总是有效,此外,这不是重点 - 套接字只是正确关闭资源的一个好主意的模式的示例。您当然可以关闭计算机电源,然后重新启动。如果文件系统有日志记录,它将恢复。 (2认同)
  • @Ben这是因为在线程中引发异常时,这是一个Python异常,只能通过Python解释器进行访问。由于time.sleep是释放GIL的外部C函数,因此直到该函数返回,重新获取GIL并运行Python字节码解释器后,它才会意识到它已发生异常。Ctrl + C会触发系统级信号,然后将其转换为Python异常。这就是为什么它可以与“ time.sleep”一起使用的原因。 (2认同)
  • 确实应该有一种标准的库方式来向 Python 线程发送异步信号。如果您想使此功能安全,那么我们需要使用带有掩码和括号的 pselect 或 Haskell 来遵循 C 示例。在任何情况下,当线程正在执行阻塞操作并且没有办法告诉它停止正在执行的操作时,如果不求助于哨兵值或重写 Python 的异常机制来引发异常(尽管是标准库的抛出方式),这在任何情况下都是非常尴尬的线程的例外也有效)。 (2认同)
  • @Jérôme:在 CPython 上,一个简单的布尔值就可以了。但是使用`Event`意味着: 1) 需要休眠一段时间的线程可以通过调用带有`timeout`的`Event`的`wait`方法来替换`time.sleep`,从而允许它被唤醒如果被告知退出,则立即执行,并且 2) 在 JIT 的非 CPython 解释器上,不存在 GIL 来提供保护,例如确保一个线程中的写入被另一个线程看到(如果代码已完全编译,它可以读取boolean 一次并假设它永远不需要再次读取它,就像没有原子的 C 代码一样)。“事件”不存在这个问题。 (2认同)

Mar*_*wis 112

没有官方API可以做到这一点,没有.

您需要使用平台API来终止线程,例如pthread_kill或TerminateThread.您可以通过pythonwin或ctypes访问此类API.

请注意,这本质上是不安全的.它可能会导致无法收集的垃圾(来自堆栈帧的局部变量变成垃圾),并且如果被杀死的线程在被杀死时具有GIL,则可能导致死锁.

  • 如果有问题的线程持有GIL,它*将导致死锁. (22认同)

cfi*_*cfi 84

一个multiprocessing.ProcessCANp.terminate()

在我想杀死一个线程但不想使用flags/locks/signals/semaphores/events /的情况下,我将线程提升为完整的进程.对于仅使用几个线程的代码,开销并不是那么糟糕.

例如,这可以轻松终止执行阻塞I/O的帮助程序"线程"

转换很简单:在相关的代码替换所有threading.Threadmultiprocessing.Process所有queue.Queuemultiprocessing.Queue,并添加所需的呼叫p.terminate()到要杀死其子你的父进程p

Python文档

  • `multiprocessing`很好,但要注意参数被腌制到新进程.因此,如果其中一个参数是不可选择的(如`logging.log`),那么使用`multiprocessing`可能不是一个好主意. (5认同)
  • `multiprocessing` 参数在 Windows 上被腌制到新进程,但 Linux 使用分叉来复制它们(Python 3.7,不确定其他版本)。所以你最终会得到在 Linux 上运行但在 Windows 上引发 pickle 错误的代码。 (2认同)

sch*_*o72 64

如果您尝试终止整个程序,可以将该线程设置为"守护程序".请参阅 Thread.daemon

  • Raffi我认为他建议你提前设置它,知道当你的主线程退出时你也希望守护程序线程退出. (25认同)
  • @MichelePiccolini:恰恰相反:守护线程*不会*在其他进程消失时保持进程运行。 (3认同)
  • 如果您希望线程即使主程序关闭也能继续运行,是否会将线程设置为守护进程? (2认同)
  • 这对我来说是最好的答案,我只想在父进程关闭时清理线程。谢谢! (2认同)

Joh*_*lin 36

这是基于thread2 - killable线程(Python配方)

您需要调用PyThreadState_SetasyncExc(),它只能通过ctypes获得.

这仅在Python 2.7.3上进行了测试,但它可能适用于其他最近的2.x版本.

import ctypes

def terminate_thread(thread):
    """Terminates a python thread from another thread.

    :param thread: a threading.Thread instance
    """
    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:
        # """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(thread.ident, None)
        raise SystemError("PyThreadState_SetAsyncExc failed")
Run Code Online (Sandbox Code Playgroud)


Jon*_*mbs 34

正如其他人所提到的,常规是设置一个停止标志.对于轻量级的东西(没有Thread的子类,没有全局变量),lambda回调是一个选项.(注意括号中的if stop().)

import threading
import time

def do_work(id, stop):
    print("I am thread", id)
    while True:
        print("I am thread {} doing something".format(id))
        if stop():
            print("  Exiting loop.")
            break
    print("Thread {}, signing off".format(id))


def main():
    stop_threads = False
    workers = []
    for id in range(0,3):
        tmp = threading.Thread(target=do_work, args=(id, lambda: stop_threads))
        workers.append(tmp)
        tmp.start()
    time.sleep(3)
    print('main: done sleeping; time to stop the threads.')
    stop_threads = True
    for worker in workers:
        worker.join()
    print('Finis.')

if __name__ == '__main__':
    main()
Run Code Online (Sandbox Code Playgroud)

print()使用pr()始终刷新(sys.stdout.flush())的函数替换可以提高shell输出的精度.

(仅在Windows/Eclipse/Python3.3上测试)

  • 在 Linux / Python 2.7 上经过验证,效果非常好。这应该是官方的答案,简单多了。 (3认同)
  • 已在 Linux Ubuntu Server 17.10/Python 3.6.3 上验证并正常运行。 (3认同)

ang*_*son 33

你不应该在没有合作的情况下强行杀死一个线程.

杀死一个线程会删除尝试/最终阻止设置的任何保证,这样你就可以锁定锁,打开文件等.

唯一一次你可以争辩强行杀死线程是一个好主意是快速杀死程序,但绝不是单线程.

  • 为什么要告诉一个主题是如此困难,当你完成当前循环时请自杀...我不明白. (11认同)
  • 在cpu中没有内置机制来识别"循环"本身,你可以期望的最好的方法是使用某种信号,使得当前循环内的代码一旦退出就会检查.处理线程同步的正确方法是通过协作方式,线程的暂停,恢复和终止是用于调试器和操作系统的功能,而不是应用程序代码. (2认同)
  • @Mehdi:如果我(亲自)在线程中编写代码,是的,我同意你的观点.但有些情况下我正在运行第三方库,而且我无法访问该代码的执行循环.这是所请求功能的一个用例. (2认同)
  • @DanH 对于第三方代码来说**更糟糕**,因为你不知道它会造成什么损害。如果您的第三方库不够强大,需要被杀死,那么您应该执行以下操作之一:(1)要求作者解决问题,(2)使用其他东西。如果您确实别无选择,那么将该代码放在不同的进程中应该更安全,因为某些资源仅在单个进程中共享。 (2认同)

Pao*_*lli 24

在Python中,你根本无法直接杀死一个线程.

如果你真的不需要一个Thread(!),你可以做的,而不是使用线程,就是使用 多处理.在这里,要杀死进程,您只需调用该方法:

yourProcess.terminate()  # kill the process!
Run Code Online (Sandbox Code Playgroud)

Python将终止你的进程(在Unix上通过SIGTERM信号,而在Windows上通过TerminateProcess()调用).使用队列或管道时要注意使用它!(它可能会破坏队列/管道中的数据)

需要注意的是,multiprocessing.Eventmultiprocessing.Semaphore中的相同的方式工作,准确threading.Eventthreading.Semaphore分别.事实上,第一个是后者的克隆.

如果您真的需要使用Thread,则无法直接杀死它.但是,您可以使用"守护程序线程".事实上,在Python中,Thread可以被标记为守护进程:

yourThread.daemon = True  # set the Thread as a "daemon thread"
Run Code Online (Sandbox Code Playgroud)

当没有剩下活着的非守护程序线程时,主程序将退出.换句话说,当您的主线程(当然是非守护程序线程)将完成其操作时,即使仍有一些守护程序线程正在运行,程序也将退出.

请注意,必须daemonstart()调用方法之前设置Thread !

当然,你可以而且应该使用daemonmultiprocessing.这里,当主进程退出时,它会尝试终止其所有守护进程子进程.

最后,请注意sys.exit()并且os.kill()不是选择.

  • @fsevenm:进程与线程相同。它们在单独的内存空间中运行,因此无法轻松共享全局变量。传递参数涉及对它们进行腌制并在另一侧对它们进行取消腌制。再加上启动和运行单独进程的开销,涉及到的其他开销比简单地切换线程要多得多。在很多方面,这都是苹果与橙子的对比,所以这可能就是原因——回答你的问题。 (2认同)

Koz*_*huk 13

您可以通过在将退出线程的线程中安装trace来终止线程.有关可能的实现,请参阅附件链接.

用Python杀死一个线程

  • 此解决方案存在两个问题:(a)使用sys.settrace()安装跟踪器将使您的线程运行速度变慢.如果它的计算界限慢多达10倍.(b)在系统调用时不会影响您的线程. (4认同)
  • 这里为数不多的真正有效的答案之一 (2认同)

Gia*_*rlo 9

如果你不杀死一个线程会更好.一种方法可能是在线程的循环中引入一个"try"块,并在你想要停止线程时抛出一个异常(例如一个break/return/...停止你的for/while/...).我在我的应用程序上使用过这个功能......


Noc*_*wer 8

绝对可以实现一个Thread.stop方法,如以下示例代码所示:

import sys
import threading
import time


class StopThread(StopIteration):
    pass

threading.SystemExit = SystemExit, StopThread


class Thread2(threading.Thread):

    def stop(self):
        self.__stop = True

    def _bootstrap(self):
        if threading._trace_hook is not None:
            raise ValueError('Cannot run thread with tracing!')
        self.__stop = False
        sys.settrace(self.__trace)
        super()._bootstrap()

    def __trace(self, frame, event, arg):
        if self.__stop:
            raise StopThread()
        return self.__trace


class Thread3(threading.Thread):

    def _bootstrap(self, stop_thread=False):
        def stop():
            nonlocal stop_thread
            stop_thread = True
        self.stop = stop

        def tracer(*_):
            if stop_thread:
                raise StopThread()
            return tracer
        sys.settrace(tracer)
        super()._bootstrap()

###############################################################################


def main():
    test1 = Thread2(target=printer)
    test1.start()
    time.sleep(1)
    test1.stop()
    test1.join()
    test2 = Thread2(target=speed_test)
    test2.start()
    time.sleep(1)
    test2.stop()
    test2.join()
    test3 = Thread3(target=speed_test)
    test3.start()
    time.sleep(1)
    test3.stop()
    test3.join()


def printer():
    while True:
        print(time.time() % 1)
        time.sleep(0.1)


def speed_test(count=0):
    try:
        while True:
            count += 1
    except StopThread:
        print('Count =', count)

if __name__ == '__main__':
    main()
Run Code Online (Sandbox Code Playgroud)

Thread3类似乎运行代码比快大约33%的Thread2类.

  • 这是为线程中设置的`self .__ stop`注入检查的一种聪明方法.请注意,与此处的大多数其他解决方案一样,它实际上不会中断阻塞调用,因为只有在输入新的本地范围时才会调用跟踪函数.另外值得注意的是,`sys.settrace`确实意味着实现调试器,配置文件等,因此被认为是CPython的实现细节,并不保证在其他Python实现中存在. (3认同)
  • @dano:`Thread2`类最大的问题之一是它运行的代码大约慢了十倍.有些人可能仍然认为这是可以接受的 (3认同)

ser*_*g06 8

这是另一种方法,但代码非常干净和简单,适用于 2021 年的 Python 3.7:

import ctypes 

def kill_thread(thread):
    """
    thread: a threading.Thread object
    """
    thread_id = thread.ident
    res = ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, ctypes.py_object(SystemExit))
    if res > 1:
        ctypes.pythonapi.PyThreadState_SetAsyncExc(thread_id, 0)
        print('Exception raise failure')
Run Code Online (Sandbox Code Playgroud)

改编自这里:https ://www.geeksforgeeks.org/python- Different-ways-to-kill-a-thread/

  • 如果您使用的是 Python 3.7,那么您必须是 2018 年的时间旅行者。如果您指的是 2021 年,请提供使用 Python 3.9 的测试。`PyThreadState_SetAsyncExc` 方法只是为线程退出执行“计划”。它**不会**杀死线程,特别是当它正在执行外部 C 库时。尝试用你的方法杀死`sleep(100)`。100秒后它将被“杀死”。它与 `while flag:` -> `flag = False` 方法一样有效。 (8认同)

sny*_*nyh 6

from ctypes import *
pthread = cdll.LoadLibrary("libpthread-2.15.so")
pthread.pthread_cancel(c_ulong(t.ident))
Run Code Online (Sandbox Code Playgroud)

t是你的Thread对象.

阅读python源代码(Modules/threadmodule.cPython/thread_pthread.h)你可以看到它Thread.ident是一个pthread_t类型,所以你可以做任何事情都pthread可以在python中使用libpthread.

  • 你没有; 不是在Windows上,也不在Linux上.原因:当您执行此操作时,有问题的线程可能会保留GIL(当您调用C时,Python会释放GIL).如果是这样,您的程序将立即陷入僵局.即使它没有,最后:块也不会被执行等等,所以这是一个非常不安全的想法. (10认同)

Ami*_*har 6

可以使用以下解决方法杀死线程:

kill_threads = False

def doSomething():
    global kill_threads
    while True:
        if kill_threads:
            thread.exit()
        ......
        ......

thread.start_new_thread(doSomething, ())
Run Code Online (Sandbox Code Playgroud)

这甚至可以用于终止从主线程终止其代码在另一个模块中编写的线程。我们可以在该模块中声明一个全局变量,并使用它终止该模块中产生的线程。

我通常使用它在程序出口处终止所有线程。这可能不是终止线程的理想方法,但可能会有所帮助。


SCB*_*SCB 6

如果你是显式调用time.sleep()为你的线程(比如查询一些外部的服务)的一部分,在菲利普的方法的改进是使用了超时的eventwait()方法,无论你sleep()

例如:

import threading

class KillableThread(threading.Thread):
    def __init__(self, sleep_interval=1):
        super().__init__()
        self._kill = threading.Event()
        self._interval = sleep_interval

    def run(self):
        while True:
            print("Do Something")

            # If no kill signal is set, sleep for the interval,
            # If kill signal comes in while sleeping, immediately
            #  wake up and handle
            is_killed = self._kill.wait(self._interval)
            if is_killed:
                break

        print("Killing Thread")

    def kill(self):
        self._kill.set()
Run Code Online (Sandbox Code Playgroud)

然后运行

t = KillableThread(sleep_interval=5)
t.start()
# Every 5 seconds it prints:
#: Do Something
t.kill()
#: Killing Thread
Run Code Online (Sandbox Code Playgroud)

使用wait()而不是sleep()ing并定期检查事件的优点是您可以在更长的睡眠间隔内进行编程,线程几乎立即停止(原本应为sleep()ing时),并且我认为处理退出的代码明显更简单。

  • 为什么这篇文章被否决?这个帖子有什么问题?看起来完全符合我的需求。 (3认同)

slu*_*pet 5

我玩这个游戏很晚,但是我一直在努力解决类似的问题,以下内容似乎可以为我很好地解决问题,并且可以在守护子线程退出时让我进行一些基本的线程状态检查和清理:

import threading
import time
import atexit

def do_work():

  i = 0
  @atexit.register
  def goodbye():
    print ("'CLEANLY' kill sub-thread with value: %s [THREAD: %s]" %
           (i, threading.currentThread().ident))

  while True:
    print i
    i += 1
    time.sleep(1)

t = threading.Thread(target=do_work)
t.daemon = True
t.start()

def after_timeout():
  print "KILL MAIN THREAD: %s" % threading.currentThread().ident
  raise SystemExit

threading.Timer(2, after_timeout).start()
Run Code Online (Sandbox Code Playgroud)

产量:

0
1
KILL MAIN THREAD: 140013208254208
'CLEANLY' kill sub-thread with value: 2 [THREAD: 140013674317568]
Run Code Online (Sandbox Code Playgroud)