因为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><p><span>立即学习</span>“<a href="https://pan.quark.cn/s/00968c3c2c15" style="text-decoration: underline !important; color: blue; font-weight: bolder;" rel="nofollow" target="_blank">Python免费学习笔记(深入)</a>”;</p><div class="aritcle_card flexRow">
<div class="artcardd flexRow">
<a class="aritcle_card_img" href="/xiazai/skill8054" title="python-parallelization"><img
src="https://img.php.cn/upload/skill/000/000/081/179181013081932.jpg" alt="python-parallelization" ></a>
<div class="aritcle_card_info flexColumn">
<a href="/xiazai/skill8054" title="python-parallelization">python-parallelization</a>
<p>将顺序 Python 代码转换为并行/并发实现,用于在需要并行化 Python 代码、提升性能时使用。</p>
</div>
<a href="/xiazai/skill8054" title="python-parallelization" 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()
有没有比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。


















