Python线程触发终止后未关闭,join操作挂起问题求助
Python多线程中无法终止等待输入的线程问题解析
问题场景
在Python多线程场景中,当q2.get(timeout=4)触发超时并进入except代码块后,线程t2仍处于运行状态,持续等待用户输入;调用closing_threads函数后,t2.join()会导致程序无限挂起。
相关代码
import threading import queue def main(): q2 = queue.Queue() q1 = queue.Queue() t1 = threading.Thread(target=nothing, name='t1', args=(q1,)) t2 = threading.Thread(target=get_interrupt_from_user, name='t2', args=(q2,)) t1.start() t2.start() try: q2.get(timeout=4) except: pass closing_threads(t1, t2) def closing_threads(t1, t2): print('closing the threads') t1.join() t2.join() print(t1.is_alive()) print(t2.is_alive()) def get_interrupt_from_user(q) -> None: print('############ Thread 2 is starting! ############') interrupt = False while not interrupt: print('use KeyboardInterrupt to stop the execution') try: input() except KeyboardInterrupt: print('KeyboardInterrupt exception took place') else: print('exit by KeyboardInterrupt!!!') interrupt = True print(f'interrupt took place = {interrupt}') q.put(interrupt) def nothing(q) -> None: print('############ Thread 1 is starting! ############')
原因解析
- 线程无强制终止机制:Python线程只能自然运行到目标函数结束,没有原生的强制终止方法。
t2的核心逻辑卡在input()上,这个函数会一直阻塞等待用户输入,没有外部信号能打断它。 - 主线程未传递停止信号:主线程触发超时后,只是进入
except块,但没给t2传递任何停止指令。t2的循环只有在用户输入(触发else分支)或捕获到KeyboardInterrupt时才会终止,完全不知道主线程已经触发超时。 t2.join()挂起的本质:join()会让主线程阻塞,直到t2完全结束。但t2卡在input()等待输入,永远不会主动退出,所以主线程会一直挂在t2.join()上。
解决办法
方法1:事件标志+非阻塞输入
给t2传递一个threading.Event作为终止信号,主线程超时后设置这个标志,同时把input()改成非阻塞模式,让t2能定期检查终止信号:
import threading import queue import sys import select def main(): q2 = queue.Queue() stop_event = threading.Event() t1 = threading.Thread(target=nothing, name='t1', args=(queue.Queue(),)) t2 = threading.Thread(target=get_interrupt_from_user, name='t2', args=(q2, stop_event)) t1.start() t2.start() try: q2.get(timeout=4) except queue.Empty: print("超时触发,通知t2停止") stop_event.set() closing_threads(t1, t2) def closing_threads(t1, t2): print('closing the threads') t1.join() t2.join() print(f"t1存活状态:{t1.is_alive()}") print(f"t2存活状态:{t2.is_alive()}") def get_interrupt_from_user(q, stop_event) -> None: print('############ Thread 2 is starting! ############') interrupt = False while not interrupt and not stop_event.is_set(): print('use KeyboardInterrupt to stop the execution') # 用select实现1秒超时的非阻塞输入,同时检查停止标志 ready, _, _ = select.select([sys.stdin], [], [], 1) if ready: try: input() except KeyboardInterrupt: print('KeyboardInterrupt exception took place') else: print('exit by KeyboardInterrupt!!!') interrupt = True q.put(interrupt) print("t2线程已退出") def nothing(q) -> None: print('############ Thread 1 is starting! ############')
方法2:将t2设为守护线程
如果t2的任务不是必须完成的,可以把它设为守护线程,这样主线程结束时会自动终止t2:
# 创建t2时添加daemon=True参数 t2 = threading.Thread(target=get_interrupt_from_user, name='t2', args=(q2,), daemon=True)
注意:这种情况下不要调用t2.join(),否则还是会因为input()阻塞而挂起。
内容的提问来源于stack exchange,提问作者Itaybz
相关产品推荐
相关产品推荐

