python多线程适用于I/O密集型任务,但受GIL限制无法并行执行CPU密集型代码;启动线程需用Thread(target=func, args=(), kwargs={})后调start(),不可直接调用func();共享变量须用Lock保护临界区,避免竞态条件。

Python 多线程在 I/O 密集型任务中能提升响应速度,但受 GIL 限制,无法真正并行执行 CPU 密集型代码——别指望靠 threading 加快纯计算(比如大数组求和、加密解密),那得换 multiprocessing 或 asyncio。
怎么启动一个线程?用 threading.Thread 最直接
最常用方式是传入 target 函数和 args/kwargs 参数,调用 start() 启动,不是直接调函数。
常见错误:写成 t = Thread(target=func()) —— 这会立刻执行 func,且主线程阻塞等待返回值,根本没开线程。
正确写法示例:
立即学习“Python免费学习笔记(深入)”;
import threading import time def say_hi(name, delay=1): time.sleep(delay) print(f"Hello, {name}")
t = threading.Thread(target=say_hi, args=("Alice",), kwargs={"delay": 0.5}) t.start() t.join()
-
args必须是 tuple,单个参数记得加逗号:(value,),不是(value) -
join()不调用的话,主线程可能提前退出,子线程被强制终止(尤其脚本末尾没等待时) - 线程名默认是
Thread-N,可通过name="my_worker"显式指定,方便调试
共享变量出问题?加 threading.Lock 保护临界区
多个线程同时读写同一个变量(比如全局计数器),不加锁会导致结果错乱——这不是概率问题,是必然发生,只是时机难复现。
典型现象:counter 本该累加 100 次到 100,结果输出 92、97、甚至 63。
解决方法:用 Lock 包裹修改共享数据的代码段:
import threading counter = 0 lock = threading.Lock()
def increment(): global counter for _ in range(10): with lock: # 自动 acquire/release counter += 1
threads = [threading.Thread(target=increment) for _ in range(10)] for t in threads: t.start() for t in threads: t.join()
print(counter) # 稳定输出 100
- 别用
time.sleep()模拟“耗时操作”来测试竞态条件——它反而可能掩盖问题;真要测,用循环空转或os.write()等低层调用 -
Lock是不可重入的,同一线程重复acquire()会死锁;需要重入锁就用R Lock - 锁粒度越小越好,只包真正共享修改的几行,别把整个函数都锁住
为什么 threading.Timer 有时不触发?注意对象生命周期
threading.Timer 启动后是个独立线程,但如果创建它的对象(比如类实例)被垃圾回收,而 Timer 又没被强引用,就可能被提前销毁,回调永远不执行。
常见场景:在类方法里写 Timer(2.0, self.callback).start(),但方法返回后实例被删,Timer 就失效了。
解决办法:
- 把 Timer 对象存为实例属性:
self.timer = Timer(...); self.timer.start() - 或用弱引用管理(较少见),或改用
threading.Event+ 循环检查 - 注意:Timer 只触发一次,要周期性执行请用循环 +
Event.wait()或改用schedule库
多线程真正的难点不在语法,而在判断哪些状态是共享的、哪些操作必须原子化、以及锁的持有范围是否覆盖了所有访问路径——一个漏掉的读或写,就可能让程序在高并发下间歇性崩掉,还很难复现。