当前位置: 首页 > 科技观察

如何杀死Python线程

时间:2023-03-14 23:27:56 科技观察

经常有人问我如何杀死后台线程,这个问题的答案让很多人不高兴:线程不能被杀死。在本文中,我将向您展示在Python中终止线程的两个选项。如果我们是一个好奇的宝宝,我们可能会遇到这样一个问题,那就是:如何杀死一个Python后台线程?我们可能尝试解决这个问题,却发现无法杀死线程。然而,本文将展示两种在Python中终止线程的方法。1.线程无法结束AThreadedExample下面是一个简单的多线程示例代码。importrandomimportthreadingimporttimedefbg_thread():foriinrange(1,30):print(f'{i}of30iterations...')time.sleep(random.random())#dosomework...print(f'{i}iterationscompletedbeforeexiting.')th=threading.Thread(target=bg_thread)th.start()th.join()使用下面的命令运行程序,在下面的程序运行中,运行到第7次迭代时,按Ctrl-C中断程序,发现后台运行的程序并没有终止。在第13次迭代时,再次按下Ctrl-C中断程序,发现程序真的退出了。$pythonthread.py1of30iterations...2of30iterations...3of30iterations...4of30iterations...5of30iterations...6of30iterations...7of30iterations...^CTraceback(mostrecentcallast):文件“thread.py”,第14行,在th.join()File"/Users/mgrinberg/.pyenv/versions/3.8.6/lib/python3.8/threading.py",line1011,injoinself._wait_for_tstate_lock()File"/Users/mgrinberg/.pyenv/versions/3.8.6/lib/python3.8/threading.py",line1027,in_wait_for_tstate_lockeliflock.acquire(block,timeout):KeyboardInterrupt8of30iterations...9of30iterations...10of30iterations...11of30iterations...12of30iterations...13of30iterations...^CException忽略于:Traceback(最近一次调用):File"/Users/mgrinberg/.pyenv/versions/3.8.6/lib/python3.8/threading.py”,第1388行,in_shutdownlock.acquire()KeyboardInterrupt:这很奇怪,不是吗?原因是Python有一些逻辑会在进程退出前运行,专门针对在实际将控制权交给操作系统之前,等待任何未配置为守护线程的后台线程完成。因此,进程在其主线程运行时收到中断信号,并准备退出。首先,它需要等待后台线程结束运行。但是,这个线程对中断一无所知,这个线程只知道自己需要完成30次迭代才能运行到最后。Python在退出期间使用的等待机制具有在接收到第二个中断信号时中止的规定。这就是第二个Ctrl-C立即结束进程的原因。所以我们看到线程是杀不死的!在接下来的章节中,我们将展示Python中两种使线程及时结束的方法。2.使用守护线程如上所述,在Python退出之前,它会等待任何不是守护线程的线程。另一方面,守护线程是不会阻止Python解释器退出的线程。如何使线程成为守护线程?所有线程对象都有一个daemon属性,可以在启动线程之前将其设置为True,然后该线程将被视为守护线程。这是上面的示例应用程序,修改后的守护线程版本:importrandomimportthreadingimporttimedefbg_thread():foriinrange(1,30):print(f'{i}of30iterations...')time.sleep(random.random())#dosomework...print(f'{i}iterationscompletedbeforeexiting.')th=threading.Thread(target=bg_thread)th.daemon=Trueth.start()th.join()再次运行它并尝试中断它,发现第一个进程退出在执行Ctrl-C后立即。~$pythonx.py1of30iterations...2of30iterations...3of30iterations...4of30iterations...5of30iterations...6of30iterations...^CTraceback(mostrecentcallast):文件“thread.py”,第15行,在th.join()文件"/Users/mgrinberg/.pyenv/versions/3.8.6/lib/python3.8/threading.py",line1011,injoinself._wait_for_tstate_lock()File"/Users/mgrinberg/.pyenv/versions/3.8。6/lib/python3.8/threading.py",line1027,in_wait_for_tstate_lockeliflock.acquire(block,ti??meout):KeyboardInterrupt那么这个线程发生了什么?线程继续运行,就好像什么都没发生一样,直到Python进程终止并返回操作系统。此时线程不存在。您可能认为这实际上是一种杀死线程的方法,但考虑到以这种方式杀死线程,您必须同时杀死进程。3.使用事件对象PythonEvents使用守护线程是避免多线程程序意外中断的简单方法,但这是一个只有在进程退出时的特殊情况下才有效的技巧。不幸的是,有时应用程序可能希望在不杀死自身的情况下结束线程。另外,有些线程可能需要在退出前进行清理,而守护线程不允许这样做。那么,还有哪些其他选择?由于无法强制线程结束,因此唯一的选择是向其添加逻辑,以便在被要求时自动退出。有多种方法可以解决上述问题,但我特别喜欢的一种方法是使用Event对象。Event类由Python标准库的threading模块提供。您可以通过实例化类来创建事件对象,如下所示:exit_event=threading.Event()Event对象可以处于两种状态之一:已设置或未设置。当我们实例化和创建时,没有设置默认事件。要将事件状态更改为set,可以调用set()方法;要查明事件是否已设置,请使用is_set()方法,如果已设置,则返回True;也可以使用wait()方法来等待事件,等待的操作是阻塞的,直到设置事件(可以设置超时),核心思想是在线程需要退出的时候设置事件。然后线程需要经常(通常在循环中)检查事件的状态,并在发现事件已被设置时处理自己的终止。对于上面显示的示例,一个好的解决方案是添加一个捕获Ctrl-C中断的信号处理程序,而不是突然退出,只需设置事件并让线程优雅地结束。importrandomimportsignalimportthreadingimporttimeexit_event=threading.Event()defbg_thread():foriinrange(1,30):print(f'{i}of30iterations...')time.sleep(random.random())#dosomework...ifexit_event.is_set():breakprint(f'{i}iterationscompletedbeforeexiting.')defsignal_handler(signum,frame):exit_event.set()signal.signal(signal.SIGINT,signal_handler)th=threading.Thread(target=bg_thread)th.start()th.join()如果您尝试中断此版本的应用程序,一切看起来会更好:$pythonthread.py1of30iterations...2of30iterations...3of30iterations...4of30iterations...5of30iterations...6of30iterations...7of30iterations...^C7iterationscompletedbeforeexiting。请注意如何优雅地处理中断,以及线程能够运行循环之后发生的代码。当线程需要在退出前关闭文件句柄或数据库连接时,这很有用。它在线程退出之前运行清理代码的能力有时是避免资源泄漏所必需的。正如我上面提到的,事件对象也是可等待的:foriinrange(1,30):print(f'{i}of30iterations...')time.sleep(random.random())ifexit_event.is_set():breakOneach迭代,调用time.sleep(),它会阻塞线程。如果在线程休眠时设置了退出事件,则它无法检查事件的状态,因此在线程能够退出之前会有一小段延迟。在这种情况下,如果存在睡眠,则使用wait()方法将睡眠与事件对象的检查结合起来会更有效:foriinrange(1,30):print(f'{i}of30iterations...')ifexit_event。wait(timeout=random.random()):break这个解决方案有效地为线程提供了一个可中断的睡眠,因为事件是在线程卡在wait()调用中间时设置的,然后等待将立即返回。4.结语你知道Python中的事件对象吗?它们是更简单的同步原语之一,不仅可以用作退出信号,还可以用于线程需要等待某些外部条件发生的许多其他情况。