Python Threading多线程下print操作导致其他线程暂停问题
问题根因
故障本质是三个核心问题叠加导致的:
- 终端标准输入/输出流本身不是线程安全资源。当前实现中,
recvMSG子线程随时会调用print()写入终端,主线程同时阻塞在input()等待用户输入,两个线程并发抢占IO锁时,会直接打断input的终端状态维护,出现提示符消失、IO阻塞卡顿——并不是线程被主动暂停,是IO资源竞争导致的死等。 - Socket读操作存在多线程竞争。
sendMSG方法在发送消息后直接调用recv()读取返回,和recvMSG线程里的循环recv同时操作同一个socket连接,会出现消息被随机读走、recv永久阻塞的问题,进一步加剧卡顿。 - 跨线程事件通知缺失。代码中
event变量没有做任何线程同步,子线程收到消息后无法安全通知主线程,主线程要么长期卡input无法响应消息,要么分支逻辑完全不触发。
修复方案
所有修复围绕「IO资源单线程持有」原则设计,避免跨线程抢占:
- 第一步:隔离网络线程的IO操作,禁止recv线程直接操作终端打印
引入线程安全队列做跨线程消息传递,网络线程只负责收消息丢队列,所有终端打印、输入操作全部收归主线程执行。
初始化ServerCon时新增队列实例:
修改recvMSG方法,移除直接print逻辑:import queue # 在ServerCon的__init__方法中添加 self.msg_queue = queue.Queue()def recvMSG(self): while True: try: data = self.client.recv(2048).decode(FORMAT) if not data: sys.exit() self.msg_queue.put(data) except: break - 第二步:改造主线程输入逻辑,用非阻塞输入替代阻塞的input,保证收到消息时可以及时刷新终端
阻塞式input()会一直占住终端IO,无法在消息到来时主动刷新输出,改用select实现短轮询的非阻塞输入,每次循环先处理队列里的新消息,再检查用户输入,打印消息时自动重绘当前输入行,避免提示符丢失。import sys import select def gameLoop(self): input_buffer = "" print(">", end="", flush=True) while True: # 先清空队列里所有待展示的消息 while not self.s.msg_queue.empty(): msg = self.s.msg_queue.get() # 清空当前输入行,打印新消息 print(f"\r{' '*(len(input_buffer)+2)}\r{msg}") # 重绘提示符和已经输入的内容 print(f">{input_buffer}", end="", flush=True) # 非阻塞检测用户输入,超时0.1秒,避免CPU空转 readable, _, _ = select.select([sys.stdin], [], [], 0.1) if readable: char = sys.stdin.read(1) if char == "\n": # 输入完成,执行命令解析 parse(input_buffer, self.s) input_buffer = "" print(">", end="", flush=True) else: input_buffer += char print(char, end="", flush=True) - 第三步:移除sendMSG中的recv调用,所有socket读操作统一由recvMSG线程处理
删掉sendMSG里if self.client.recv(2048).decode(FORMAT):这段逻辑,避免两个线程同时读socket。如果需要匹配请求对应的响应,可以给消息加请求ID,通过队列传递后在主线程做匹配即可。
注意:Windows系统下select不支持监听sys.stdin,如果需要适配Windows,把输入检测部分替换为msvcrt模块的kbhit、getch实现即可,核心逻辑不变。
内容的提问来源于stack exchange,提问作者Benji
相关产品推荐
相关产品推荐

