线程互斥本质上是在解决一个问题:**多个线程同时访问共享资源时,如何保证“同一时刻只有一个线程在操作”**。不同方案的差别主要在于:是否进入内核、是否忙等、性能如何、适用场景。 我按“从底层到高级”的逻辑给你梳一遍,这样你能建立一条完整的认知链。 --- ## 1. 关闭中断(最底层,仅限内核) **思路:** 在临界区执行前关闭中断,执行完再打开。 **效果:** * 当前 CPU 不会被打断 * 不会发生线程切换 → 自然不会并发 **问题:** * 只对单核有效(多核直接失效) * 用户态做不了 * 会影响系统响应(很危险) **结论:** 这是操作系统内核里的“原始手段”,你可以理解为最暴力的锁。 --- ## 2. 原子操作(Atomic / CAS) **核心:** CPU 提供的不可分割操作,比如: * Test-and-Set * Compare-and-Swap(CAS) **典型代码逻辑:** ```c while (CAS(lock, 0, 1) == false) { // busy wait } ``` **特点:** * 不进入内核(用户态) * 极快 * 但会**忙等(spin)** --- ## 3. 自旋锁(Spinlock) 其实就是“原子操作 + 死循环”: ```c while (lock == 1); // 一直等 lock = 1; ``` 实际实现用 CAS 保证安全。 **特点:** * 不睡眠,一直占 CPU * 适合: * 临界区很短 * 多核环境 **问题:** * 临界区长 → CPU白烧 * 单核上很蠢(等自己) --- ## 4. 互斥锁(Mutex) 这是你最常用的。 **核心思想:** 拿不到锁 → 线程**睡眠(阻塞)** **流程:** 1. 尝试获取锁(CAS) 2. 失败 → 进入内核 3. 被挂起(加入等待队列) 4. 被唤醒再竞争 **特点:** * 不浪费 CPU * 需要系统调用(慢一点) **一句话理解:** > 自旋锁是“在门口死等”,Mutex 是“拿号排队等叫号”。 --- ## 5. 读写锁(Read-Write Lock) **优化场景:读多写少** 规则: * 多个读可以同时进行 * 写必须独占 **效果:** * 提升并发性能 **问题:** * 写可能被饿死(读太多) --- ## 6. 信号量(Semaphore) 本质是一个**计数器**。 ### 两种: * 二值信号量(≈ Mutex) * 计数信号量(允许 N 个线程进入) **经典操作:** * P(wait):减1,不够就阻塞 * V(signal):加1,唤醒别人 **适合:** * 资源池(比如连接池) --- ## 7. 条件变量(Condition Variable) 不是锁,是**配合 Mutex 用的** **用途:** 线程等待某个条件成立 ```c pthread_cond_wait(cond, mutex); ``` **特点:** * 自动释放 mutex * 被唤醒后重新加锁 **典型场景:** 生产者-消费者 --- ## 8. 乐观锁(无锁编程) **核心思想:** > 不加锁,赌冲突很少 用 CAS + 重试: ```c do { old = x; new = old + 1; } while (!CAS(&x, old, new)); ``` **特点:** * 无锁(lock-free) * 高性能 * 代码复杂,容易错 --- ## 9. Futex(Linux关键机制) 你前面学内核,这个必须知道。 **全称:** Fast Userspace Mutex **核心思想:** * 优先在用户态自旋 * 实在不行再进入内核睡眠 **流程:** 1. CAS尝试拿锁 2. 失败 → futex_wait(进内核) 3. 解锁 → futex_wake **一句话:** > Mutex = 自旋锁 + 睡眠(futex实现) --- ## 总结一张图(抽象层级) 从低到高: ``` 关闭中断(内核) ↓ 原子操作(CAS) ↓ 自旋锁(忙等) ↓ Mutex(阻塞) ↓ 读写锁 / 信号量 / 条件变量(高级同步) ↓ 无锁编程(CAS体系) ``` --- ## 给你一个“工程选型直觉” 你以后写程序可以这样选: * 临界区极短 → 自旋锁 * 普通情况 → Mutex * 读多写少 → 读写锁 * 资源数量控制 → 信号量 * 线程协作 → 条件变量 * 极致性能 → CAS / 无锁 --- 如果你想更进一步,我可以帮你把这些**全部映射到 Linux 内核实现(futex、task_struct、调度器)**,甚至给你画出“线程阻塞时内核到底干了什么”的完整路径,那一块才是真正把知识打通的地方。