因为print()不是原子操作:先格式化字符串,再write(),最后flush(),多线程交叉执行导致输出错乱;本质是共享stdout未同步,并非python缺陷。

为什么print在多线程里会乱?
因为 print() 不是原子操作:它先格式化字符串,再调用 sys.stdout.write(),最后刷缓冲区。多个线程同时执行这三步,就可能交叉写入——比如线程A刚写完“[INFO]”,线程B插进来写了“[ERROR]”,接着A又补上“ processing...”,结果变成 “[INFO][ERROR] processing...”。这不是Python的bug,是I/O共享资源没加锁的典型表现。
threading.Lock怎么用才不踩坑?
必须确保所有对共享输出(如 print())的调用都包裹在同一把锁下;锁对象要全局唯一,不能每个线程 new 一个新 threading.Lock()。
- ✅ 正确:在模块顶层定义
print_lock = threading.Lock(),所有线程共用它 - ❌ 错误:在每个线程函数内部写
lock = threading.Lock(),锁失效 - ⚠️ 注意:不要在锁内做耗时操作(如网络请求、sleep),否则严重拖慢并发效率
示例:
import threading
import time
<p>print_lock = threading.Lock()</p><div class="aritcle_card flexRow artxards">
<div class="artcardd flexRow">
<a class="aritcle_card_img" rel="nofollow" href="/xiazai/skill8393" title="Forge — Python Developer"><img
src="https://img.php.cn/upload/skill/000/000/081/179203503734063.jpg" alt="Forge — Python Developer" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a rel="nofollow" href="/xiazai/skill8393" title="Forge — Python Developer" class="overflowclass">Forge — Python Developer</a>
<p class="overflowclass">Python开发助手 — 自动化脚本、API封装、命令行工具、数据管道、网页爬虫。经过测试的代码,包含错误处理和文档。当你需要...</p>
</div>
<a rel="nofollow" href="/xiazai/skill8393" title="Forge — Python Developer" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span>
</a>
</div>
</div><p>def worker(name):
with print_lock: # 自动 acquire/release
print(f"[{name}] started")
time.sleep(0.1)
print(f"[{name}] done")</p><p>threads = [threading.Thread(target=worker, args=(f"t{i}",)) for i in range(3)]
for t in threads:
t.start()
for t in threads:
t.join()
</p>
有没有比Lock更轻量的替代方案?
有,但要看场景。如果只是避免打印错乱,print(..., flush=True) 能减少缓冲干扰,但不能彻底解决竞态——它只保证单次 print 的原子性,不保证多行逻辑的顺序。例如:
# 即使加了flush,这两行仍可能被其他线程打断
print("Step 1", flush=True)
print("Step 2", flush=True) # ← 中间可能插入别的线程输出
所以真正需要保持多行语义完整(比如日志头+内容+时间戳),还是得用 threading.Lock 或改用线程安全的日志库(如 logging 默认就带锁)。
logging.getLogger() 真的比手动print+Lock更好吗?
是的,而且不是“稍微好一点”,是设计层面就规避了问题:logging 的 Handler 在 emit 阶段自动加锁,且支持格式化、级别控制、文件输出等,无需手动管理锁生命周期。
- ✅ 推荐:用
logging.info("msg")替代print(),默认线程安全 - ⚠️ 注意:如果自定义了
Handler且没调用self.acquire()/self.release(),那就不安全 - ? 小技巧:开发时可快速启用线程名显示:
logging.basicConfig(format="%(threadName)s - %(message)s")
锁本身不难,难的是意识到:不是所有“看起来像输出”的操作都天然线程安全;而最容易被忽略的,是那些你以为“就一行代码,肯定没问题”的 print。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










