C++ 自旋锁 (SpinLock) 实现分析
代码概述
#include <iostream>
#include <atomic>
#include <thread>
class SpinLock {
public:
void lock() {
while (flag.test_and_set(std::memory_order_acquire)); // 自旋等待,直到成功获取到锁
}
void unlock() {
flag.clear(std::memory_order_release); // 释放锁
}
private:
std::atomic_flag flag = ATOMIC_FLAG_INIT;
};
核心组件分析
1. 头文件说明
-
<iostream>: 标准输入输出库(本例未直接使用,但通常并发程序会用到) -
<atomic>: C++11 原子操作库,提供线程安全的原子类型 -
<thread>: C++11 线程库,用于多线程编程
2. SpinLock 类结构
私有成员
std::atomic_flag flag = ATOMIC_FLAG_INIT;
-
std::atomic_flag: 是 C++ 中最基本的原子类型,代表一个布尔标志位 -
ATOMIC_FLAG_INIT: 宏定义,用于将atomic_flag初始化为 clear(false)状态 - 必须通过初始化器列表或默认构造函数初始化,不能赋值初始化
公共方法
lock() 方法
void lock() {
while (flag.test_and_set(std::memory_order_acquire)); // 自旋等待,直到成功获取到锁
}
-
test_and_set(): 原子操作,将 flag 设置为 true 并返回操作前的值 - 自旋等待:当 flag 已被设置(锁被占用),循环继续;当 flag 未被设置(锁可用),设置为 true 并退出循环
-
std::memory_order_acquire: 内存获取序,确保后续读取操作不会被重排到该操作之前
unlock() 方法
void unlock() {
flag.clear(std::memory_order_release); // 释放锁
}
-
clear(): 原子操作,将 flag 设置为 false -
std::memory_order_release: 内存释放序,确保之前的写入操作不会被重排到该操作之后
工作原理
-
初始化:
flag被初始化为ATOMIC_FLAG_INIT,即 clear 状态(false) -
获取锁:
- 线程调用
lock()方法 -
test_and_set()原子地检查 flag 状态并设置为 true - 如果返回 false(之前是 clear),表示成功获取锁,退出循环
- 如果返回 true(之前是 set),表示锁已被占用,继续循环(自旋等待)
- 线程调用
-
释放锁:
- 线程调用
unlock()方法 -
clear()原子地将 flag 设置为 false - 其他自旋等待的线程可以获取锁
- 线程调用
内存序的重要性
std::memory_order_acquire
- 确保在获取锁后,对共享数据的读取操作不会被重排到锁获取之前
- 保证了线程看到的是锁释放前的最新数据
std::memory_order_release
- 确保在释放锁前,对共享数据的写入操作不会被重排到锁释放之后
- 保证了线程对共享数据的修改对其他线程可见
自旋锁的优缺点
优点
- 低延迟:没有上下文切换开销,适合锁持有时间短的场景
- 简单实现:基于原子操作,实现简单可靠
- 无死锁风险:只要线程最终会释放锁,就不会死锁
缺点
- CPU 资源浪费:线程在自旋等待时会占用 CPU 资源
- 优先级反转:高优先级线程可能被低优先级线程阻塞
- 不适合锁持有时间长的场景:会导致大量 CPU 资源浪费
使用场景
- 锁持有时间短:如保护简单的共享变量或短临界区
- 多核 CPU:在多核环境下,自旋等待不会影响其他核心的线程
- 低延迟要求:如实时系统或高性能计算
与其他锁的比较
| 锁类型 | 优点 | 缺点 |
|---|---|---|
| 自旋锁 | 低延迟、无上下文切换 | CPU 资源浪费 |
| 互斥锁 | 不浪费 CPU 资源 | 上下文切换开销大 |
| 读写锁 | 支持并发读操作 | 实现复杂 |
代码优化建议
- 添加超时机制:避免无限自旋
- 指数退避:自旋一段时间后增加等待时间
- 结合线程优先级:确保高优先级线程优先获取锁
示例用法
#include <iostream>
#include <thread>
#include <vector>
#include "spinlock.h" // 假设 SpinLock 定义在这个头文件中
SpinLock spinLock;
int counter = 0;
void increment() {
for (int i = 0; i < 100000; ++i) {
spinLock.lock();
++counter;
spinLock.unlock();
}
}
int main() {
std::vector<std::thread> threads;
for (int i = 0; i < 10; ++i) {
threads.emplace_back(increment);
}
for (auto& t : threads) {
t.join();
}
std::cout << "Counter: " << counter << std::endl; // 应该输出 1000000
return 0;
}
总结
这段代码实现了一个基于 std::atomic_flag 的自旋锁,通过原子操作和内存序保证了多线程环境下的线程安全。自旋锁适合锁持有时间短、低延迟要求的场景,但会消耗 CPU 资源。理解内存序的作用对于正确使用原子操作和实现线程同步至关重要。