You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Python Threading多线程下print操作导致其他线程暂停问题

问题根因

故障本质是三个核心问题叠加导致的:

  1. 终端标准输入/输出流本身不是线程安全资源。当前实现中,recvMSG子线程随时会调用print()写入终端,主线程同时阻塞在input()等待用户输入,两个线程并发抢占IO锁时,会直接打断input的终端状态维护,出现提示符消失、IO阻塞卡顿——并不是线程被主动暂停,是IO资源竞争导致的死等。
  2. Socket读操作存在多线程竞争。sendMSG方法在发送消息后直接调用recv()读取返回,和recvMSG线程里的循环recv同时操作同一个socket连接,会出现消息被随机读走、recv永久阻塞的问题,进一步加剧卡顿。
  3. 跨线程事件通知缺失。代码中event变量没有做任何线程同步,子线程收到消息后无法安全通知主线程,主线程要么长期卡input无法响应消息,要么分支逻辑完全不触发。
修复方案

所有修复围绕「IO资源单线程持有」原则设计,避免跨线程抢占:

  • 第一步:隔离网络线程的IO操作,禁止recv线程直接操作终端打印
    引入线程安全队列做跨线程消息传递,网络线程只负责收消息丢队列,所有终端打印、输入操作全部收归主线程执行。
    初始化ServerCon时新增队列实例:
    import queue
    # 在ServerCon的__init__方法中添加
    self.msg_queue = queue.Queue()
    
    修改recvMSG方法,移除直接print逻辑:
    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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.27 12:45:32