atomic 与内存序
定义与作用
std::atomic<T> 提供不可分割的原子操作,memory_order 控制操作的同步和内存可见性。
std::atomic<int> counter{0};
counter.fetch_add(1, std::memory_order_relaxed); // 原子递增
std::atomic<bool> flag{false};
flag.store(true, std::memory_order_release); // 带释放语义的写入
核心原理
六种内存序
CAS(Compare-And-Swap)循环
完整示例
示例一:飞翔科技无锁统计系统
场景说明:小崔用无锁原子操作实现高性能请求统计。
#include <iostream>
#include <thread>
#include <atomic>
#include <vector>
#include <chrono>
#include <iomanip>
struct RequestStats {
// 原子成员
std::atomic<uint64_t> totalRequests{0};
std::atomic<uint64_t> successCount{0};
std::atomic<uint64_t> errorCount{0};
std::atomic<uint64_t> totalLatencyUs{0}; // 总延迟(微秒)
std::atomic<uint64_t> maxLatencyUs{0};
// 记录一次请求
void record(bool success, uint64_t latencyUs) {
totalRequests.fetch_add(1, std::memory_order_relaxed);
if (success)
successCount.fetch_add(1, std::memory_order_relaxed);
else
errorCount.fetch_add(1, std::memory_order_relaxed);
totalLatencyUs.fetch_add(latencyUs, std::memory_order_relaxed);
// 更新最大延迟(CAS 循环)
uint64_t currentMax = maxLatencyUs.load(std::memory_order_relaxed);
while (latencyUs > currentMax &&
!maxLatencyUs.compare_exchange_weak(
currentMax, latencyUs,
std::memory_order_relaxed,
std::memory_order_relaxed))
{ /* 重试 */ }
}
// 快照(非原子但一致性足以满足监控需求)
struct Snapshot {
uint64_t total;
uint64_t success;
uint64_t error;
uint64_t totalLatencyUs;
uint64_t maxLatencyUs;
};
Snapshot snapshot() const {
return {
totalRequests.load(std::memory_order_relaxed),
successCount.load(std::memory_order_relaxed),
errorCount.load(std::memory_order_relaxed),
totalLatencyUs.load(std::memory_order_relaxed),
maxLatencyUs.load(std::memory_order_relaxed),
};
}
};
int main() {
std::cout << "=== 飞翔科技无锁请求统计 ===\n\n";
RequestStats stats;
auto worker = [&stats](int id, int iterations) {
// 模拟不同延迟
uint64_t baseLatency = 100 + (id % 5) * 50;
for (int i = 0; i < iterations; ++i) {
bool success = (i % 10 != 0); // 90% 成功率
uint64_t lat = baseLatency + (i % 20);
stats.record(success, lat);
std::this_thread::sleep_for(std::chrono::microseconds(1));
}
};
const int kThreads = 8;
const int kIterPerThread = 5000;
std::vector<std::thread> threads;
auto start = std::chrono::steady_clock::now();
for (int i = 0; i < kThreads; ++i)
threads.emplace_back(worker, i, kIterPerThread);
for (auto& t : threads) t.join();
auto end = std::chrono::steady_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start).count();
auto snap = stats.snapshot();
std::cout << std::fixed << std::setprecision(1);
std::cout << "总请求: " << snap.total << "\n";
std::cout << "成功: " << snap.success << "\n";
std::cout << "失败: " << snap.error << "\n";
std::cout << "成功率: " << (100.0 * snap.success / snap.total) << "%\n";
std::cout << "平均延迟: "
<< (static_cast<double>(snap.totalLatencyUs) / snap.total)
<< " us\n";
std::cout << "最大延迟: " << snap.maxLatencyUs << " us\n";
std::cout << "耗时: " << ms << " ms\n";
std::cout << "吞吐量: " << (snap.total * 1000 / ms) << " req/s\n";
}
预期输出:
=== 飞翔科技无锁请求统计 ===
总请求: 40000
成功: 36000
失败: 4000
成功率: 90.0%
平均延迟: XXX.X us
最大延迟: XXX us
耗时: XXX ms
吞吐量: XXXXX req/s
逐段分析:
fetch_add是原子 RMW(读-改-写)操作,memory_order_relaxed对计数器足够compare_exchange_weak实现无锁最大延迟更新,可能在虚假失败后重试weak版本可能在值匹配时也失败(spurious failure),适合循环中重试strong版本仅在值不匹配时失败,开销更大- 所有原子成员在 struct 中默认为零初始化
示例二:release/acquire 实现自旋锁
场景说明:大翔展示用原子布尔实现最简单的自旋锁。
#include <iostream>
#include <thread>
#include <atomic>
#include <vector>
#include <string>
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;
};
// 飞翔科技打印队列
SpinLock printLock;
int globalLogId = 0;
void secureLog(const std::string& msg) {
std::lock_guard<SpinLock> lock(printLock); // 配合自定义锁类型
std::cout << "[" << ++globalLogId << "] " << msg << "\n";
}
void worker(int id) {
for (int i = 0; i < 3; ++i) {
secureLog("服务器 " + std::to_string(id) + " 心跳 #" + std::to_string(i));
std::this_thread::sleep_for(std::chrono::milliseconds(10));
}
}
int main() {
std::cout << "=== 飞翔科技自旋锁日志 ===\n\n";
std::vector<std::thread> threads;
for (int i = 1; i <= 4; ++i)
threads.emplace_back(worker, i);
for (auto& t : threads) t.join();
std::cout << "\n所有 " << globalLogId << " 条日志完整输出\n";
// 释放-获取语义说明
std::cout << "\n--- 内存序说明 ---\n";
std::cout << "lock() 中的 test_and_set(memory_order_acquire)\n";
std::cout << " → 后续读写不会重排到此操作之前\n";
std::cout << "unlock() 中的 clear(memory_order_release)\n";
std::cout << " → 之前的写入对后续 acquire 可见\n";
std::cout << "两者配对形成 synchronizes-with 关系\n";
}
预期输出:
=== 飞翔科技自旋锁日志 ===
[1] 服务器 1 心跳 #0
[2] 服务器 2 心跳 #0
...
所有 12 条日志完整输出
--- 内存序说明 ---
lock() 中的 test_and_set(memory_order_acquire)
→ 后续读写不会重排到此操作之前
unlock() 中的 clear(memory_order_release)
→ 之前的写入对后续 acquire 可见
两者配对形成 synchronizes-with 关系
逐段分析:
atomic_flag是唯一保证始终无锁的原子类型test_and_set原子地设置为 true 并返回旧值acquire保证临界区内的操作不会重排到lock()之前release保证临界区内的操作在unlock()之前对其他线程可见ATOMIC_FLAG_INIT初始化原子标志
易错场景与面试考点
易错场景
1. atomic 不等于无锁
std::atomic<BigStruct> x; // 可能使用内部锁而非真正的无锁
// 使用 x.is_lock_free() 检查
2. compare_exchange_weak 虚假失败
int expected = x.load();
do {
int desired = compute(expected);
} while (!x.compare_exchange_weak(expected, desired));
// 即使 expected == x,weak 也可能失败,需要循环
3. relaxed 误用
// ❌ 用 relaxed 做同步标志
data = 42;
ready.store(true, std::memory_order_relaxed); // 其他线程可能看不到 data=42
// ✅ 用 release/acquire
ready.store(true, std::memory_order_release); // data 写入 happens-before
while (!ready.load(std::memory_order_acquire)); // 可见 data=42
面试考点
| 考点 | 要点 |
|---|---|
| atomic 保证 | 原子性(不可分割)、可见性(多线程)、顺序性 |
| memory_order 六种 | 从强到弱:seq_cst, acq_rel, release, acquire, consume, relaxed |
| 默认顺序 | memory_order_seq_cst,最强但开销最大 |
| CAS 循环 | compare_exchange_weak/strong,无锁编程的核心模式 |
| atomic_flag | 唯一保证始终无锁的类型 |
| is_lock_free() | 运行时检测是否真正无锁 |
| fetch_add/sub | 原子加减,返回旧值 |
| atomic 不可拷贝 | 拷贝构造和赋值被删除 |