你写了个程序,开了两个线程一起往同一个变量里加数字,结果最后值不对——不是少加了,就是多加了。这不是代码写错了,是典型的“线程抢资源”问题。
为什么需要同步?
想象食堂打饭:窗口只有一个,但七八个人同时挤过去递饭卡。没排队的话,可能刷了两次卡只打了一份饭,或者饭打了两份却只扣一次钱。线程也一样,CPU 切换飞快,两个线程几乎同时读、改、写一个变量,中间一插队,数据就乱了。
最常用的同步工具:锁(Lock)
就像给食堂窗口装个闸机,一次只放一个人进去。Java 里用 synchronized,Python 用 threading.Lock,C# 用 lock 关键字。
Python 示例:
import threading
counter = 0
lock = threading.Lock()
def add_one():
global counter
for _ in range(100000):
with lock:
counter += 1
t1 = threading.Thread(target=add_one)
t2 = threading.Thread(target=add_one)
t1.start()
t2.start()
t1.join()
t2.join()
print(counter) # 稳稳输出 200000除了锁,还有啥?
锁太“霸道”,容易卡住别人。有些场景更适合轻量级方案:
- 原子操作:像 Python 的
queue.Queue,内部已做好同步,直接put()/get()就行; - 信号量(Semaphore):控制最多几个线程能进某段代码,比如限制数据库连接池最多 5 个并发;
- 条件变量(Condition):适合“等通知”场景,比如生产者造好数据,才通知消费者来取。
小心死锁
两个线程各拿一把锁,又都等着对方手里的另一把——谁也不松手,就僵住了。典型例子:线程 A 先锁住账户 X 再去锁 Y,线程 B 偏偏先锁 Y 再锁 X。解决办法很简单:所有线程按固定顺序拿锁,比如永远先 X 后 Y。
同步不是万能膏药。过度加锁会拖慢速度,该异步的别硬塞同步。关键是在“安全”和“效率”之间找个舒服的平衡点。