# c++中自旋锁与互斥锁的性能对比及适用场景
面试时没有回答好问题是很正常的,被面试官质疑时,要有今天不会明天会的底气,面后做好复盘,不在同一道题目上跌倒第二次,今天就从 自旋锁与互斥锁的性能对 背起来
# 简要回答
自旋锁(Spinlock)通过忙等待实现同步,在用户态循环检查锁状态,适用于锁持有时间短的场景。
互斥锁(Mutex)通过系统调用实现阻塞,线程会进入睡眠状态,适用于锁持有时间较长的场景。
自旋锁避免上下文切换开销但浪费CPU周期,互斥锁节省CPU资源但引入切换开销。
# 详细回答
自旋锁特性:
实现机制:在用户空间忙等待,不断循环检测锁状态
CPU消耗:高,即使等待也在持续消耗CPU周期
上下文切换:无,线程始终保持运行状态
响应延迟:低,锁释放后能立即获取
实现复杂度:相对简单,通常基于原子操作
互斥锁特性:
实现机制:通过操作系统内核调度,线程阻塞并进入睡眠
CPU消耗:低,等待时不消耗CPU资源
上下文切换:有,涉及用户态到内核态切换
响应延迟:高,需要唤醒睡眠线程
实现复杂度:相对复杂,依赖操作系统支持
性能关键因素:
锁竞争强度:高竞争下自旋锁性能急剧下降
临界区大小:短临界区适合自旋锁,长临界区适合互斥锁
CPU核心数:多核系统更适合自旋锁
调度策略:实时系统可能偏好自旋锁
# 代码示例
// 自旋锁实现
#include <atomic>
#include <thread>
#include <iostream>
class Spinlock {
private:
std::atomic_flag flag = ATOMIC_FLAG_INIT;
public:
void lock() {
// 自旋等待,直到成功获取锁
while (flag.test_and_set(std::memory_order_acquire)) {
// 可选的优化:在重度竞争时让出CPU
// std::this_thread::yield();
}
}
void unlock() {
flag.clear(std::memory_order_release);
}
};
// 使用示例
Spinlock spinlock;
int shared_data = 0;
void spinlock_worker(int id) {
for (int i = 0; i < 1000; ++i) {
spinlock.lock();
shared_data++; // 短临界区操作
spinlock.unlock();
}
std::cout << "Thread " << id << " finished" << std::endl;
}
//------------------------------------------------------------------------
//互斥锁实现
#include <mutex>
#include <thread>
#include <iostream>
std::mutex mtx;
int shared_data = 0;
void mutex_worker(int id) {
for (int i = 0; i < 1000; ++i) {
std::lock_guard<std::mutex> lock(mtx);
shared_data++; // 短临界区操作
// 模拟较长临界区操作
// std::this_thread::sleep_for(std::chrono::microseconds(10));
}
std::cout << "Thread " << id << " finished" << std::endl;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
# 知识拓展
- 知识图解

- 适用场景
- 自旋锁适用场景:
a.多核处理器系统 - 等待线程不会阻塞其他核心的工作
b.实时系统 - 需要确定性响应时间,避免调度延迟
c.内核开发 - 在中断处理程序中不能睡眠
d.极短临界区 - 锁持有时间小于两次上下文切换开销
e.低竞争场景 - 很少出现多个线程同时竞争锁
- 互斥锁适用场景:
a.单核处理器 - 自旋锁在单核上浪费CPU时间
b.长临界区操作 - 锁持有时间较长(>10-20μs)
c.高竞争场景 - 多个线程频繁竞争同一锁
d.功耗敏感应用 - 需要减少不必要的CPU消耗
e.用户空间程序 - 可以利用操作系统调度器
- 面试官可能追问
Q1: 在单核CPU上为什么通常不建议使用自旋锁?
A1: 在单核CPU上,自旋锁会浪费宝贵的CPU时间。如果线程A持有锁,线程B自旋等待,但线程A无法运行来释放锁,因为CPU被线程B占用,这就造成了死锁般的状态。只有在可抢占内核或者配合主动让出CPU的情况下,单核系统才能有限使用自旋锁。
Q2: 自旋锁会不会引起活锁?
A2:会的。如果多个线程在同一时刻都尝试获取自旋锁,可能会出现"惊群效应",所有线程都在自旋但都无法获取锁。解决方法包括:
使用test-and-set等原子操作
引入随机退避机制
使用票据锁保证公平性
Q3: 如何确定自旋锁的自旋次数上限?
A3:可以通过以下因素综合考虑:
上下文切换的大致开销(通常1-10μs)
临界区的平均执行时间
CPU核心数量
系统负载情况 一般建议自旋次数为上下文切换开销的1.5-2倍,或者使用自适应策略动态调整。
评论
验证登录状态...