Python多线程关键在理解GIL仅限Python字节码执行、I/O时释放,CPU密集型才受阻;共享变量须用Lock或Queue防护;调试靠线程命名与enumerate。

Python多线程不是“开几个Thread就完事”,真正卡住多数人的,是GIL、共享状态、线程安全这些底层逻辑没理清。这一讲不堆代码,专攻“为什么这样写才对”。
GIL到底锁住了什么?别再背“Python不能并行”了
GIL(全局解释器锁)只限制同一时刻只有一个线程执行Python字节码,但它不锁I/O操作、不锁C扩展、不锁系统调用。所以: - 文件读写、网络请求、time.sleep() 期间,GIL会释放,其他线程可运行 - 纯CPU密集型任务(如大数组计算、递归)才真被GIL拖慢 - 想突破GIL?用multiprocessing(进程)、numba、Cython,或把计算交给asyncio+协程
共享变量=定时炸弹?线程安全的三个关键动作
多个线程读写同一个变量(比如全局list、dict、类属性),必须主动防护: - 用threading.Lock:临界区前后加lock.acquire()/lock.release(),推荐用with语句自动管理 - 避免直接共享可变对象:优先用queue.Queue传递数据(它内部已加锁) - 慎用局部变量+闭包:看似不共享,但若闭包引用了外部可变对象,仍可能出问题
实战案例:并发下载10个网页,怎么写才不翻车?
目标:同时发起10个HTTP请求,汇总响应长度。错误写法是用全局列表append;正确结构如下:
(示例逻辑,非完整代码)- 创建一个threading.Lock实例 - 每个线程拿到响应后,用with lock: results.append(len(html)) - 或更优:用queue.Queue()代替列表,主线程从队列取结果 - 加上超时控制(requests.get(timeout=5))和异常捕获,防止某个线程卡死拖垮全部
调试多线程程序的两个硬招
现象难复现?别靠print猜: - 用threading.enumerate() 查看当前活跃线程数 - 用threading.current_thread().name 给每个线程命名,日志里一眼分清谁在干啥 - 遇到死锁?加timeout参数(Lock.acquire(timeout=2)),让线程失败退出而非无限等










