线程同步:互斥锁/自旋锁/读写锁/条件变量

一句话:多个线程访问共享数据,不加锁就乱套。锁就是让它们”排队”的工具。

互斥锁=厕所门栓,自旋锁=一直敲门,读写锁=读可以一起读但写只能一个人写,条件变量=等奶来了再吃饼干。

一、四种同步机制的类比速览


mindmap

  root((线程同步 4种方式))

    互斥锁 Mutex

      一个人进去锁门

      其他人门外排队

    自旋锁 Spinlock

      一直敲门问"好了没?"

      不睡觉,死磕

    读写锁 RWLock

      读可以一起读

      写必须独占

    条件变量 Condition Variable

      等条件满足再干活

      不空转,睡着了等人叫

一句话类比
互斥锁一次只能一个人进去,其他人睡觉等厕所门栓
自旋锁一次只能一个人进去,其他人一直在门口转悠一直敲门问”好了没”
读写锁读可以一起读,写必须独占图书馆自习室 vs 私人包间
条件变量等某个条件满足再干活,等的时候不浪费 CPU等奶来了再吃饼干

2. 自旋锁(Spinlock)——互斥锁的”急性子”版本

它长啥样

 
# 自旋锁的概念——不睡觉,一直问
 
class SpinLock:
 
    def __init__(self):
 
        self.locked = False
 
    def acquire(self):
 
        while self.locked:
 
            pass  # 一直转圈,不睡觉!
 
            # CPU 空转:"好了没?好了没?好了没?"
 
        self.locked = True
 
    def release(self):
 
        self.locked = False
 

和互斥锁最核心的区别

互斥锁自旋锁
拿不到锁时阻塞(睡觉)空转(一直问)
CPU 占用不占 CPU占着 CPU 空转
切换开销有(休眠→唤醒)没有(不用切换)
适合场景锁的时间长锁的时间极短

互斥锁: 拿不到 → 睡觉 → 醒来有开销(像深度睡眠再起床)

自旋锁: 拿不到 → 一直问 → 不睡觉(像一直敲键盘 F5 刷新)

生活类比

你女朋友在卫生间化妆,你要问她拿车钥匙:

锁类型你的行为体力消耗
互斥锁在门口沙发上躺着等,她出来你再起来不费体力
自旋锁站在门口每隔 2 秒敲一次门:“好了没?好了没?“费体力(占 CPU)

如果她只是洗个手(10 秒)— 自旋锁更好,你刚躺下她就出来了(省了躺下→起来→走过去的时间)

如果她在化妆(30 分钟)— 互斥锁更好,你躺着睡觉,醒来她刚好出来

自旋锁的核心哲学: 我就等一会儿,不值得去睡一觉再起来

Python 里没有自旋锁

 
# Python threading 库只有互斥锁 Lock
 

优缺点

优点缺点
等待时不用线程切换(快)占着 CPU 空转(浪费)
锁的时间极短时比互斥锁快得多锁的时间长了就是灾难

适用场景: 锁的时间极短(微秒级)。比如:修改一个变量、更新一个计数器、操作一行数据。内核里大量用自旋锁——因为内核里很多操作就是微秒级

4. 条件变量(Condition Variable)——“等一个特定条件”

为什么需要它?

前面三种锁都是管资源的互斥访问——“一次只能一个人碰数据”。

条件变量管的是另一件事:等某个条件满足再干活

互斥锁解决的是: “别抢!”

条件变量解决的是: “等一会儿!“

一个经典场景

生产者-消费者问题:

生产者: 做好了数据,放到队列里

消费者: 从队列里取数据,处理

问题: 消费者去队列里拿数据 — 空的 → 怎么办?

  • 一直轮询检查? → 浪费 CPU

  • 睡觉等通知? → 条件变量!

 
import threading
 
import time
 
import random
 
queue = []
 
cond = threading.Condition()
 
def producer():
 
    while True:
 
        time.sleep(random.random() * 2)
 
        with cond:
 
            item = random.randint(1, 100)
 
            queue.append(item)
 
            print(f"生产了 {item}")
 
            cond.notify()        # 通知消费者:有货了!
 
def consumer():
 
    while True:
 
        with cond:
 
            while not queue:
 
                cond.wait()      # 没货?睡吧,等通知
 
            item = queue.pop(0)
 
            print(f"消费了 {item}")
 

核心方法

方法作用类比
wait()在条件变量上,释放锁,线程睡觉”没货?我先睡了,有货叫我”
notify()叫醒一个等待的线程”来货了,醒醒!“
notify_all()叫醒所有等待的线程”全体都有,醒醒!“

完整的工作流程

消费者:

 
with cond:           # 先拿锁
 
  while queue为空:    # 检查条件
 
    cond.wait()      # 没货,睡觉(自动释放锁)
 
  data = queue取数据  # 有货了,干活
 
                      # 出 with 自动释放锁
 

生产者:

 
with cond:           # 拿锁
 
  queue放数据         # 生产
 
  cond.notify()      # 通知消费者:有货了
 
                      # 出 with 自动释放锁
 

wait 干的三件事

 
cond.wait()
 
# 3. 被 notify() 叫醒后,重新拿锁
 

生活类比

🍪 等饼干

 
你(消费者)想吃饼干 → 打开饼干盒 → 空的
 
互斥锁方案:每 5 秒来看一次 → 累死了,还费电(占 CPU)
 
条件变量方案:去睡觉 → 你妈(生产者)买了饼干 → 叫你一声"饼干了!" → 你醒来吃
 

条件变量和互斥锁有啥关系?

条件变量不单独存在,它必须搭配一个互斥锁

  • wait() 会释放锁 → 别人才能修改条件

  • 被唤醒后会重新拿锁 → 安全地检查条件

适用场景: 需要等待某个条件满足。比如:队列空等生产、缓存未命中等加载、子线程完成等结果

二、选型决策树


graph TD

    A[多个线程访问共享数据] --> B{主要是读还是写?}

    B -->|读多写少| C[读写锁]

    B -->|写多| D{锁的时间多长?}

    D -->|极短 微秒级| E[自旋锁]

    D -->|较长 毫秒以上| F[互斥锁]

    A --> G{需要等某个条件?}

    G -->|是| H[条件变量 + 互斥锁]

    G -->|否| B

三、记忆口诀

互斥锁,最常用,拿不到就睡觉

自旋锁,急性子,拿不到就转圈

读写锁,最聪明,读能一起写独占

条件变量等人叫,等奶来了泡饼干


总结:互斥锁锁门、自旋锁空转、读写锁读共享写独占、条件变量等通知。四个工具解决不同场景的线程同步问题。

速记卡(面试闪卡)

Q1:一句话讲清「线程同步:互斥锁/自旋锁/读写锁/条件变量」到底是什么?

A:四种线程同步工具:互斥锁、自旋锁、读写锁、条件变量,分别解决排队、空转、读写分离与等待条件。

Q2:一、四种同步机制的类比速览 —— 怎么理解?

A:四把”排队神器”:互斥锁像厕所门栓(一人进、他人睡等);自旋锁像一直敲门(不睡、空转问”好了没”);读写锁像自习室(多人一起读、写时独占);条件变量像等妈喊”饼干好了”(条件满足才动)。一句话——别抢、别空转、读共享、等通知。

Q3:二、选型决策树 —— 怎么理解?

A:像”按场景选工具”:读多写少→读写锁;写且锁极短(微秒)→自旋锁;写且较长(毫秒+)→互斥锁;要等某条件→条件变量+互斥锁。内核里大量用自旋(操作快),应用层多用互斥(省 CPU)。

Q4:三、记忆口诀 —— 怎么理解?

A:现成四句顺口溜最省事:互斥锁最常用、拿不到就睡;自旋锁急性子、拿不到就转;读写锁最聪明、读一起写独占;条件变量等人叫、等奶来了泡饼干。面试背这四句就能展开讲。

Q5:四把锁的适用边界 —— 怎么理解?

A:补充边界:互斥锁 Python 有(threading.Lock),自旋锁 Python 没原生(靠 GIL 也意义不大,多见于 C/内核);读写锁适合缓存、配置等”读远多于写”;条件变量必须配互斥锁,wait 会释放锁、被 notify 叫醒再抢锁。四者不互斥,常组合用。

Q6:核心速记主线有哪些?

  • 互斥锁:拿不到就阻塞睡眠,适合锁时间较长

  • 自旋锁:拿不到就空转忙等,适合微秒级极短锁

  • 读写锁:读共享、写独占,适合读多写少

  • 条件变量:配互斥锁等待条件,wait 释放锁、notify 唤醒

口诀

A:互斥睡觉自旋转,

读写共享写独占;

条件等人配互斥,

四锁各管一段路。

相关链接