Python 中 类变量 vs 全局变量【多线程访问区别】
底层上两者都是共享状态,都存在线程不安全问题,但是查找路径、修改行为、隔离性有差异。 Python 多线程受 GIL 限制:同一时刻只有 1 个线程执行字节码,但不是原子操作依然会有竞态。
1. 存储位置
- 全局变量:存放在模块的
__dict__(模块字典),属于整个模块共享。 - 类变量:存放在类对象的
__dict__,所有实例共享这份类变量;实例没有重写赋值时,读的时候才去类上找。
class A: x = 0 # 类变量,在A.__dict__ g = 0 # 全局变量,当前模块 __dict__2. 读取操作(多线程读)
只读场景下:
- 全局变量:去模块命名空间查找
- 类变量:
A.x直接访问类字典;obj.x先查实例 dict,找不到再向上找类 dict
✅只读不加写操作:两者都安全,无竞态,只是查找链路略有差别,性能微小差距。
3. 修改操作(核心区别,多线程写)
① 全局变量
g = 0 def func(): global g g += 1g +=1不是原子:读取 → 计算 → 写回。 多线程并发执行,会丢更新,竞态条件。 必须global声明,否则会当成局部变量。
② 类变量
class A: x = 0 def func(): A.x += 1A.x +=1同样不是原子操作,一样会丢数据,线程不安全。
⚠️ 坑:
obj.x = xxx不是修改类变量!而是给实例新增实例变量,不会改动类上的值。
a = A() a.x = 100 # 新建实例属性,A.x 不变!这是高频踩坑点4. 作用域与隔离差异(多线程场景最关键区别)
- 全局变量:整个模块全部线程共享,作用域是模块级别。所有函数、所有线程都能直接读写。一旦污染,影响面极大。
- 类变量:共享范围仅限这个类和它的实例。
- 不同类之间类变量互不干扰;
- 如果你希望多组独立共享状态,可以定义多个类,天然分组隔离;
- 全局变量做分组隔离就很麻烦,需要手动维护多个全局变量。
5. 锁使用上没有本质差别
不管是全局变量,还是类变量,并发写都需要锁保护:
from threading import Lock lock = Lock() class A: x = 0 def thread_func(): for _ in range(10000): with lock: A.x += 1全局变量写法也是一样加锁。GIL 不会保护+=这类复合操作。
6. 对比总结表
表格
| 维度 | 全局变量 | 类变量 |
|---|---|---|
| 共享范围 | 整个模块所有代码、线程 | 同一个类的所有实例、类本身 |
| 存储位置 | 模块__dict__ | 类对象__dict__ |
| 并发读 | 安全 | 安全 |
| 并发写 | 非原子,线程不安全 | 非原子,线程不安全 |
| 修改陷阱 | 忘记写 global,变成局部变量 | 实例.属性=值不会修改类变量,新增实例变量 |
| 状态分组 | 很难,容易全局污染 | 方便,多个类天然隔离多组共享状态 |
| 生命周期 | 模块加载创建,模块销毁释放 | 类加载创建,类被 GC 回收销毁 |
7. 实践建议
- 多线程共享状态:优先类变量封装,把共享变量 + 锁 + 操作方法封装到类内,代码更清晰、隔离好。mp.scel.cn/blog/9kLm2QpX7a.html
mp.scel.cn/blog/Zx4Tn8Vr1B.html
mp.scel.cn/blog/Qw7Yh3Ns5D.html
mp.scel.cn/blog/Mc2Jv9Kp6E.html
mp.scel.cn/blog/Rt8Bn4Xz0F.html
mp.scel.cn/blog/Hg5Lp1Wq7G.html
mp.scel.cn/blog/Uv3Dm6Yr2H.html
mp.scel.cn/blog/Pa9Kc4Nt8J.html
mp.scel.cn/blog/Se2Qx7Vb5K.html
mp.scel.cn/blog/Fn6Wm1Zp3L.html
mp.scel.cn/blog/Yd4Hr9Cv2M.html
mp.scel.cn/blog/Jk7Tq3Bs8N.html
mp.scel.cn/blog/Ol1Pm5Xw6P.html
mp.scel.cn/blog/Bg8Nc2Vr4Q.html
mp.scel.cn/blog/Xm5Kt7Yd1R.html
mp.scel.cn/blog/Cv9Qp3Ln6S.html
mp.scel.cn/blog/Zr2Wm8Hx4T.html
mp.scel.cn/blog/Nk6Jd1Vp9U.html
mp.scel.cn/blog/Lq4Yt7Bc2V.html
mp.scel.cn/blog/Wp8Rm3Nx5W.html
mp.scel.cn/blog/Td1Kv6Qs9X.html
mp.scel.cn/blog/Hx5Pm2Zr7Y.html
mp.scel.cn/blog/Gc9Bn4Wt1Z.html
mp.scel.cn/blog/Vr3Lq8Jd6A.html
mp.scel.cn/blog/Mp7Yx2Kc5B.html
mp.scel.cn/blog/Ks4Wm9Nv3C.html
mp.scel.cn/blog/Qn1Rt6Bp8D.html
mp.scel.cn/blog/Dz5Jv2Xm7E.html
mp.scel.cn/blog/Sb9Lq4Hr1F.html
mp.scel.cn/blog/Yt3Pm8Kx6G.html
mp.scel.cn/blog/Rc7Wn1Vd4H.html
mp.scel.cn/blog/Bp2Qx9Lm5J.html
mp.scel.cn/blog/Nv6Tr3Yc8K.html
mp.scel.cn/blog/Lm1Jd7Wp2L.html
mp.scel.cn/blog/Xq5Kv9Bn4M.html
mp.scel.cn/blog/Ct8Rm2Hx7N.html
mp.scel.cn/blog/Zp3Yq6Ld1P.html
mp.scel.cn/blog/Gv9Wm4Nc5Q.html
mp.scel.cn/blog/Hk2Jx7Rt8R.html
mp.scel.cn/blog/Md6Pq1Bv3S.html
mp.scel.cn/blog/Tn8Lm5Wx2T.html
mp.scel.cn/blog/Qr4Yp9Kc7U.html
mp.scel.cn/blog/Vx1Jv6Nd3V.html
mp.scel.cn/blog/Kp7Wm2Ht9W.html
mp.scel.cn/blog/Bc5Rq8Lx4X.html
mp.scel.cn/blog/Nm9Yd3Pv1Y.html
mp.scel.cn/blog/Lt2Kx7Bc6Z.html
mp.scel.cn/blog/Xv8Qm4Nr5A.html
mp.scel.cn/blog/Cp1Jd9Wt3B.html
mp.scel.cn/blog/Zk6Lq2Yx8C.html
mp.scel.cn/blog/Gr3Pm7Vc1D.html
mp.scel.cn/blog/Hx9Bn5Kd4E.html
mp.scel.cn/blog/Mv2Tq8Lm6F.html
mp.scel.cn/blog/Tc7Yp1Rx3G.html
mp.scel.cn/blog/Qm4Jv9Wd5H.html
mp.scel.cn/blog/Vp8Km2Nx7J.html
mp.scel.cn/blog/Kd1Lq6Bt9K.html
mp.scel.cn/blog/Bx5Rm3Yc2L.html
mp.scel.cn/blog/Nq9Pv7Hd4M.html
mp.scel.cn/blog/Lm3Jx1Wt6N.html
mp.scel.cn/blog/Xc8Qm5Bv2P.html
mp.scel.cn/blog/Cv4Kd9Nr7Q.html
mp.scel.cn/blog/Zp1Yq6Lx3R.html
mp.scel.cn/blog/Gt7Wm2Vc9S.html
mp.scel.cn/blog/Hk5Jv8Rd1T.html
mp.scel.cn/blog/Mx9Pq3Bn6U.html
mp.scel.cn/blog/Tm2Ld7Wv4V.html
mp.scel.cn/blog/Qc6Yp1Kx8W.html
mp.scel.cn/blog/Vr3Jm9Nt5X.html
mp.scel.cn/blog/Kp8Qv4Bd2Y.html
mp.scel.cn/blog/Bm1Lx6Wc7Z.html
mp.scel.cn/blog/Nv5Rd9Yp3A.html
mp.scel.cn/blog/Lq2Km7Jx8B.html
mp.scel.cn/blog/Xt9Pm4Vc1C.html
mp.scel.cn/blog/Cd3Qv8Bn6D.html
mp.scel.cn/blog/Zx7Lp2Yr5E.html
mp.scel.cn/blog/Gm1Jd6Wv9F.html
mp.scel.cn/blog/Hv4Kq9Nx2G.html
mp.scel.cn/blog/Mp8Rm3Bc7H.html
mp.scel.cn/blog/Tq1Yv5Ld4J.html
mp.scel.cn/blog/Qx6Jm2Wt9K.html
mp.scel.cn/blog/Vc3Pq7Rn1L.html
mp.scel.cn/blog/Km9Ld4Bv6M.html
mp.scel.cn/blog/Bp2Xv8Yc3N.html
mp.scel.cn/blog/Nr5Qm1Jx7P.html
mp.scel.cn/blog/Lv8Kd3Wt2Q.html
mp.scel.cn/blog/Xm4Pq9Bc6R.html
mp.scel.cn/blog/Ct1Rv7Lx5S.html
mp.scel.cn/blog/Zp8Jm2Yd4T.html
mp.scel.cn/blog/Gq3Kv6Nx9U.html
mp.scel.cn/blog/Hm7Ld1Wc5V.html
mp.scel.cn/blog/Mv4Pq8Bt2W.html
mp.scel.cn/blog/Tr9Ym3Jx6X.html
mp.scel.cn/blog/Qc2Kv7Ld1Y.html
mp.scel.cn/blog/Vp6Rm4Nc8Z.html
mp.scel.cn/blog/Kx1Jd9Wv3A.html
mp.scel.cn/blog/Bm5Qq2Yt7B.html
mp.scel.cn/blog/Nc8Lm4Hx1C.html
mp.scel.cn/blog/Lv3Pq9Rd6D.html
mp.scel.cn/blog/Xt7Km2Bc5E.html - 尽量少用全局变量,全局状态容易被任意函数意外修改,调试困难。
- 无论用哪一种,只要存在并发写,都必须加锁。
如果你想,我可以写一段完整可运行代码,演示类变量多线程丢值和全局变量多线程丢值的对比。