本文将详细介绍如何在 x86 架构上正确使用 compare_exchange_weak
,并通过一个实际示例展示如何避免竞态条件。
compare_exchange_weak
compare_exchange_weak
是 C++11 提供的一种原子操作,用于在多线程环境下实现无锁编程。它尝试将一个变量从期望值更改为新值,并返回操作是否成功。为了保证操作的原子性和顺序一致性,需要指定内存序列参数。参数包括:
memory_order_acquire
:在加载操作前执行。memory_order_release
:在存储操作后执行。memory_order_relaxed
:不做任何内存顺序保证,仅用于性能优化。memory_order_seq_cst
:顺序一致的内存序列,确保操作的全局顺序一致性。
默认情况下,compare_exchange_weak
使用 memory_order_seq_cst
内存序列,可以避免竞态条件。但是,有时为了优化性能,会尝试使用较弱的内存序列(如 memory_order_acquire
和 memory_order_release
),这时候要小心处理,以免引入竞态条件。
参考代码
#include <cstdint>
#include <atomic>
#include <limits>
#include <thread>
#include <vector>
struct WriterLock
{
// 尝试获取写锁
void writer_lock()
{
while (true)
{
// 加载当前计数
uint32_t prev_readers = _reader_count.load(std::memory_order_acquire);
if (prev_readers == 0)
{
// 尝试将计数从 0 变为 HAS_WRITER,使用较弱的内存序列以提高性能
if (_reader_count.compare_exchange_weak(prev_readers, HAS_WRITER, std::memory_order_acquire, std::memory_order_relaxed))
{
// 成功获取写锁,返回
return;
}
}
}
}
// 释放写锁
void writer_unlock()
{
while (true)
{
// 加载当前计数
uint32_t prev_readers = _reader_count.load(std::memory_order_acquire);
// 如果当前持有写锁
if (prev_readers == HAS_WRITER)
{
// 尝试将读者计数从 HAS_WRITER 变为 0,释放写锁
if (_reader_count.compare_exchange_weak(prev_readers, 0, std::memory_order_release, std::memory_order_relaxed))
{
// 成功释放写锁,返回
return;
}
}
}
}
private:
const uint32_t HAS_WRITER = std::numeric_limits<uint32_t>::max(); // 表示写锁已被占用
std::atomic<uint32_t> _reader_count{0}; // 计数
};
uint64_t counter{0}; // 全局计数器
WriterLock lock; // 写锁实例
std::atomic<bool> go{false}; // 控制线程开始的标志
static const uint64_t num_times = uint64_t(10'000'000); // 每个线程递增计数器的次数
void increment()
{
// 等待开始信号
while(!go) {}
for(uint64_t j = 0; j < num_times; ++j)
{
lock.writer_lock(); // 获取写锁
++counter; // 递增计数器
lock.writer_unlock(); // 释放写锁
}
}
int main()
{
std::vector<std::thread> threads; // 线程容器
const size_t num_threads = 10; // 线程数
// 创建并启动线程
for(uint64_t t = 0; t < num_threads; ++t)
{
threads.push_back(std::thread(increment));
}
// 分离线程(允许它们独立运行)
for(uint64_t t = 0; t < num_threads; ++t)
{
threads[t].detach();
}
go = true; // 设置开始信号,启动所有线程
}
结论
通过正确配置 compare_exchange_weak
的内存序列参数,可以在 x86 架构上实现高效的线程安全写锁。