【C/C++】多线程下自旋锁的行为逻辑
文章目录
- 多线程下自旋锁的行为逻辑
- 1 自旋锁的基本逻辑
- 2 线程状态分析
- 3 自旋锁缺点
- 4 自旋锁优化方式
- 5 多核场景
- 总结
多线程下自旋锁的行为逻辑
如果是自旋锁(spinlock)逻辑,当一个线程已经持有锁时,其他线程会不断尝试获取锁,处于“忙等(busy-waiting)”状态,不会主动休眠、挂起或进入阻塞态,除非你显式地这么写。
1 自旋锁的基本逻辑
std::atomic_flag lock = ATOMIC_FLAG_INIT;void lock_spin() {while (lock.test_and_set(std::memory_order_acquire)) {// 自旋:啥也不做,继续尝试获取锁}
}void unlock_spin() {lock.clear(std::memory_order_release);
}
test_and_set()
会原子地设置lock
为true
,并返回旧值。- 如果另一个线程已设置
lock = true
,那么当前线程会进入while
循环 —— 这就是“忙等”。 - 自旋期间线程不会释放 CPU,也不会进入 sleep 或 yield,仍然在占用计算资源。
2 线程状态分析
线程状态 | 说明 |
---|---|
持锁线程 | 正常执行,直到 unlock() |
自旋线程 | CPU 上循环尝试获取锁(高 CPU 占用),处于 Running 状态 |
阻塞线程 | 不是自旋锁的行为,阻塞/挂起是互斥锁或条件变量做的事 |
在操作系统中,自旋线程的状态在调度器眼中通常是 “running”,如果被抢占则为 “ready”,而不是 “waiting” 或 “sleeping”。
3 自旋锁缺点
- CPU 浪费大:线程处于忙等状态,浪费 CPU 周期。
- 在多核下高竞争时非常低效。
- 如果持锁线程长时间执行(或阻塞),自旋线程将毫无意义地消耗资源。
4 自旋锁优化方式
- 加上
pause
/yield
提示 CPU 优化自旋:
while (lock.test_and_set(std::memory_order_acquire)) {std::this_thread::yield(); // Hint 给操作系统让出 CPU
}
- 自旋 + 回退 + sleep(适合高争用):
int count = 0;
while (lock.test_and_set(std::memory_order_acquire)) {if (++count > 1000) {std::this_thread::sleep_for(std::chrono::microseconds(10)); // 主动退让}
}
5 多核场景
- 可以有多个线程处于“运行态”吗?
可以有多个线程处于“就绪态(ready)”或“运行态(running)”,但实际同时运行的线程数量受限于 CPU 核心数。
-
操作系统的线程调度器会维护多个状态:
Running
:正在某个 CPU 核上执行。Ready
:准备好运行,但暂时没分配到 CPU。Waiting/Sleeping/Blocked
:在等 I/O、锁、条件变量等。
-
如果你的系统是 4 核 CPU,那么最多可以同时运行 4 个线程(真正的并行)。
-
其他线程即使“处于运行态”,但本质上是 ready 状态,被 OS 调度器等待调度。
- CPU 怎么决定“分配给谁”?
由操作系统的 线程调度器(scheduler) 决定。调度策略有多种:
策略 | 描述 |
---|---|
时间片轮转(round-robin) | 每个线程轮流使用 CPU |
优先级调度 | 优先级高的线程更容易获得 CPU |
多队列反馈 | 结合线程运行情况动态调整优先级 |
调度器会考虑:
- 线程优先级(
nice
值、real-time 等) - 当前负载(load balancing)
- CPU cache affinity(避免 cache miss)
- 多核负载均衡(尽量让线程平均分布到各个核心)
- 多核 CPU 下的多线程运行模型
- 多核 = 真正的“并行”执行
如果你有 8 核 CPU,你的 8 个线程可以真正在同一时间点并行执行,每个线程占用一个核心。线程之间通过共享内存、缓存一致性协议(如 MESI)等机制保持同步。
示例:4 个线程,4 核 CPU
时间 | 核心 0 | 核心 1 | 核心 2 | 核心 3 |
---|---|---|---|---|
T0 | 线程 A | 线程 B | 线程 C | 线程 D |
T1 | 线程 A | 线程 B | 线程 E | 线程 F |
同时最多执行 4 个线程,剩下的在线程队列中等待调度。
- 自旋锁下,多线程抢锁是怎样的?
- 每个核心上的线程在同时执行
while (lock.test_and_set())
,此时所有核的线程都在自旋。 - 哪个线程在某一时刻成功把 lock 从 false 设置为 true,它就赢得了执行权,进入临界区。
- 其他线程仍然自旋,不会被挂起。
-
举个真实案例:8线程自旋 + 4核 CPU
-
线程 A 拿到自旋锁。
-
线程 B~H 也在尝试拿锁,它们会在各自 CPU 核或被调度时执行
while
自旋。 -
一旦线程 A 执行完
unlock()
,调度器从剩下的线程中挑选一个抢锁成功。 -
其余线程继续自旋。
总结
情况 | 自旋锁表现 |
---|---|
锁未被持有 | 线程获取锁,正常执行 |
锁被持有 | 自旋线程持续运行,占用 CPU |
高并发场景 | 多线程争用,自旋浪费严重 |
最佳用途 | 锁持有时间非常短的临界区,如 CPU 缓存级并发控制 |
问题 | 回答 |
---|---|
多个线程可以运行吗? | 可以,但并发度受限于 CPU 核数 |
谁获得 CPU 执行? | 操作系统调度器决定,基于策略 |
多核是怎么处理多线程的? | 各核独立运行线程,实现真正并行 |
自旋锁时线程状态? | 在运行或就绪态,不会自动 sleep |