欢迎光临
我们一直在努力

如何理解 C++ 中的 atomic?

C++ 中的 atomic 深入理解

什么是 atomic?

std::atomic 是 C++11 引入的模板类,用于实现原子操作。原子操作是不可分割的操作,在多线程环境下不会被其他线程打断,保证了操作的原子性、可见性和有序性。

#include <atomic>
#include <iostream>
#include <thread>

// ❌ 非原子操作:数据竞争
int counter = 0;
void unsafeIncrement() {
counter++; // 不是原子操作,可能导致数据竞争
}

// ✓ 原子操作:线程安全
std::atomic<int> atomicCounter(0);
void safeIncrement() {
atomicCounter++; // 原子操作,线程安全
}

为什么需要 atomic?

1. 解决数据竞争

#include <atomic>
#include <thread>
#include <vector>

// 数据竞争示例
void dataRaceExample() {
int value = 0;

// 多个线程同时修改 value
std::thread t1([&value]() {
for (int i = 0; i < 100000; ++i) {
value++; // ❌ 数据竞争
}
});

std::thread t2([&value]() {
for (int i = 0; i < 100000; ++i) {
value++; // ❌ 数据竞争
}
});

t1.join();
t2.join();

// 结果可能不是 200000,而是小于 200000 的某个值
std::cout << "Non-atomic result: " << value << "\\n";
}

// 使用 atomic 解决
void atomicExample() {
std::atomic<int> value(0);

std::thread t1([&value]() {
for (int i = 0; i < 100000; ++i) {
value++; // ✓ 原子操作
}
});

std::thread t2([&value]() {
for (int i = 0; i < 100000; ++i) {
value++; // ✓ 原子操作
}
});

t1.join();
t2.join();

// 结果一定是 200000
std::cout << "Atomic result: " << value << "\\n";
}

2. 内存序保证

#include <atomic>
#include <thread>
#include <iostream>

std::atomic<bool> ready{false};
int data = 0;

void producer() {
data = 42; // 写入数据
ready = true; // 设置标志
}

void consumer() {
while (!ready) { // 等待标志
// 自旋等待
}
std::cout << "Data: " << data << "\\n"; // 读取数据
}

void memoryOrderExample() {
std::thread t1(producer);
std::thread t2(consumer);

t1.join();
t2.join();
}

atomic 的核心特性

1. 原子性(Atomicity)

操作要么完全执行,要么完全不执行,不会被中断。

std::atomic<int> value(0);

// 以下操作都是原子的
value.store(10); // 原子存储
int x = value.load(); // 原子加载
value++; // 原子自增
value.fetch_add(5); // 原子加法
value.exchange(20); // 原子交换

2. 可见性(Visibility)

一个线程的修改对其他线程立即可见。

#include <atomic>
#include <thread>
#include <iostream>

std::atomic<int> flag{0};

void thread1() {
flag.store(1, std::memory_order_release); // 释放语义
}

void thread2() {
while (flag.load(std::memory_order_acquire) != 1) {
// 获取语义,保证能看到 thread1 的所有写入
}
std::cout << "Flag is set\\n";
}

3. 有序性(Ordering)

保证操作的执行顺序,防止编译器和 CPU 重排序。

#include <atomic>
#include <thread>

std::atomic<int> x{0};
std::atomic<int> y{0};

void writeX() {
x.store(1, std::memory_order_release);
}

void writeY() {
y.store(1, std::memory_order_release);
}

void readXThenY() {
while (x.load(std::memory_order_acquire) != 1) {}
if (y.load(std::memory_order_acquire) == 0) {
std::cout << "Y is still 0\\n";
}
}

void readYThenX() {
while (y.load(std::memory_order_acquire) != 1) {}
if (x.load(std::memory_order_acquire) == 0) {
std::cout << "X is still 0\\n";
}
}

atomic 的基本操作

1. 基础操作

#include <atomic>
#include <iostream>

void basicOperations() {
std::atomic<int> value(0);

// store – 原子存储
value.store(10);
std::cout << "After store(10): " << value << "\\n";

// load – 原子加载
int x = value.load();
std::cout << "Loaded value: " << x << "\\n";

// exchange – 原子交换
int old = value.exchange(20);
std::cout << "Exchanged " << old << " with 20\\n";

// compare_exchange_weak – 弱 CAS
int expected = 20;
bool success = value.compare_exchange_weak(expected, 30);
std::cout << "CAS weak: " << (success ? "success" : "failed") << "\\n";

// compare_exchange_strong – 强 CAS
expected = 30;
success = value.compare_exchange_strong(expected, 40);
std::cout << "CAS strong: " << (success ? "success" : "failed") << "\\n";
}

2. 算术操作

void arithmeticOperations() {
std::atomic<int> value(10);

// fetch_add – 原子加法
int old = value.fetch_add(5);
std::cout << "fetch_add(5): old=" << old << ", new=" << value << "\\n";

// fetch_sub – 原子减法
old = value.fetch_sub(3);
std::cout << "fetch_sub(3): old=" << old << ", new=" << value << "\\n";

// fetch_and – 原子按位与
old = value.fetch_and(0x0F);
std::cout << "fetch_and(0x0F): old=" << old << ", new=" << value << "\\n";

// fetch_or – 原子按位或
old = value.fetch_or(0xF0);
std::cout << "fetch_or(0xF0): old=" << old << ", new=" << value << "\\n";

// fetch_xor – 原子按位异或
old = value.fetch_xor(0xFF);
std::cout << "fetch_xor(0xFF): old=" << old << ", new=" << value << "\\n";

// operator++ 和 operator–
++value; // 前置自增
value++; // 后置自增
value; // 前置自减
value; // 后置自减

// 复合赋值
value += 5;
value -= 3;
value &= 0x0F;
value |= 0xF0;
value ^= 0xFF;
}

3. 指针操作

void pointerOperations() {
int data[10] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9};
std::atomic<int*> ptr(data);

// 原子指针算术
int* old = ptr.fetch_add(2);
std::cout << "fetch_add(2): old=" << (old data)
<< ", new=" << (ptr.load() data) << "\\n";

old = ptr.fetch_sub(1);
std::cout << "fetch_sub(1): old=" << (old data)
<< ", new=" << (ptr.load() data) << "\\n";

// 前置/后置自增自减
++ptr; // ptr += 1
ptr++; // 返回旧值,然后 +1
ptr; // ptr -= 1
ptr; // 返回旧值,然后 -1
}

内存序(Memory Order)

内存序是 atomic 最复杂但也最重要的概念。

内存序类型

enum class memory_order {
relaxed, // 最宽松,只保证原子性
consume, // 消费语义(很少使用)
acquire, // 获取语义
release, // 释放语义
acq_rel, // 获取+释放
seq_cst // 顺序一致性(默认)
};

1. memory_order_relaxed

#include <atomic>
#include <thread>
#include <iostream>

std::atomic<int> x{0};
std::atomic<int> y{0};

void relaxedExample() {
// relaxed 只保证原子性,不保证顺序
std::thread t1([]() {
x.store(1, std::memory_order_relaxed);
y.store(1, std::memory_order_relaxed);
});

std::thread t2([]() {
int r1 = y.load(std::memory_order_relaxed);
int r2 = x.load(std::memory_order_relaxed);
std::cout << "r1=" << r1 << ", r2=" << r2 << "\\n";
// 可能看到 r1=1, r2=0(重排序)
});

t1.join();
t2.join();
}

2. memory_order_acquire / memory_order_release

#include <atomic>
#include <thread>
#include <iostream>

std::atomic<int> data{0};
std::atomic<bool> ready{false};

void acquireReleaseExample() {
// 生产者
std::thread producer([]() {
data.store(42, std::memory_order_relaxed); // 写入数据
ready.store(true, std::memory_order_release); // 释放语义
});

// 消费者
std::thread consumer([]() {
while (!ready.load(std::memory_order_acquire)) { // 获取语义
// 等待
}
// 保证能看到 data=42
std::cout << "Data: " << data.load(std::memory_order_relaxed) << "\\n";
});

producer.join();
consumer.join();
}

3. memory_order_seq_cst

#include <atomic>
#include <thread>
#include <iostream>

std::atomic<int> x{0};
std::atomic<int> y{0};
std::atomic<int> z{0};

void seqCstExample() {
// 顺序一致性:所有线程看到相同的操作顺序
std::thread t1([]() {
x.store(1, std::memory_order_seq_cst);
});

std::thread t2([]() {
y.store(1, std::memory_order_seq_cst);
});

std::thread t3([]() {
int r1 = x.load(std::memory_order_seq_cst);
int r2 = y.load(std::memory_order_seq_cst);
std::cout << "Thread 3: r1=" << r1 << ", r2=" << r2 << "\\n";
});

std::thread t4([]() {
int r3 = y.load(std::memory_order_seq_cst);
int r4 = x.load(std::memory_order_seq_cst);
std::cout << "Thread 4: r3=" << r3 << ", r4=" << r4 << "\\n";
});

t1.join();
t2.join();
t3.join();
t4.join();
}

内存序选择指南

// 1. 简单计数器 – relaxed
std::atomic<int> counter{0};
counter.fetch_add(1, std::memory_order_relaxed);

// 2. 生产者-消费者模式 – acquire/release
std::atomic<bool> ready{false};
std::atomic<int> data{0};

// 生产者
data.store(value, std::memory_order_relaxed);
ready.store(true, std::memory_order_release);

// 消费者
while (!ready.load(std::memory_order_acquire)) {}
int value = data.load(std::memory_order_relaxed);

// 3. 需要全局顺序 – seq_cst(默认)
std::atomic<int> flag{0};
flag.store(1); // 默认是 seq_cst

CAS(Compare-And-Swap)操作

1. 基本用法

#include <atomic>
#include <iostream>

void casExample() {
std::atomic<int> value(10);

// compare_exchange_weak
int expected = 10;
if (value.compare_exchange_weak(expected, 20)) {
std::cout << "CAS succeeded, value is now " << value << "\\n";
} else {
std::cout << "CAS failed, expected is now " << expected << "\\n";
}

// compare_exchange_strong
expected = 20;
if (value.compare_exchange_strong(expected, 30)) {
std::cout << "CAS succeeded, value is now " << value << "\\n";
} else {
std::cout << "CAS failed, expected is now " << expected << "\\n";
}
}

2. weak vs strong

void weakVsStrong() {
std::atomic<int> value(10);

// weak: 可能失败(虚假失败),但性能更好
int expected = 10;
while (!value.compare_exchange_weak(expected, 20)) {
// 循环重试
}

// strong: 保证不会虚假失败,但性能稍差
expected = 20;
if (value.compare_exchange_strong(expected, 30)) {
std::cout << "Success\\n";
}
}

3. 无锁栈实现

#include <atomic>
#include <iostream>

template<typename T>
class LockFreeStack {
private:
struct Node {
T data;
Node* next;
Node(const T& value) : data(value), next(nullptr) {}
};

std::atomic<Node*> head;

public:
LockFreeStack() : head(nullptr) {}

void push(const T& value) {
Node* newNode = new Node(value);
newNode->next = head.load(std::memory_order_relaxed);

// CAS 操作
while (!head.compare_exchange_weak(
newNode->next,
newNode,
std::memory_order_release,
std::memory_order_relaxed
)) {
// 循环重试
}
}

bool pop(T& result) {
Node* oldHead = head.load(std::memory_order_acquire);

while (oldHead && !head.compare_exchange_weak(
oldHead,
oldHead->next,
std::memory_order_acquire,
std::memory_order_relaxed
)) {
// 循环重试
}

if (oldHead) {
result = oldHead->data;
delete oldHead;
return true;
}
return false;
}
};

void lockFreeStackExample() {
LockFreeStack<int> stack;

stack.push(1);
stack.push(2);
stack.push(3);

int value;
while (stack.pop(value)) {
std::cout << "Popped: " << value << "\\n";
}
}

atomic 与锁的对比

#include <atomic>
#include <mutex>
#include <thread>
#include <iostream>
#include <chrono>

// 使用 mutex
class CounterWithMutex {
private:
std::mutex mtx;
int value = 0;
public:
void increment() {
std::lock_guard<std::mutex> lock(mtx);
++value;
}

int get() const {
std::lock_guard<std::mutex> lock(mtx);
return value;
}
};

// 使用 atomic
class CounterWithAtomic {
private:
std::atomic<int> value{0};
public:
void increment() {
++value;
}

int get() const {
return value;
}
};

void performanceComparison() {
const int iterations = 1000000;

// 测试 mutex
CounterWithMutex counterMutex;
auto start1 = std::chrono::high_resolution_clock::now();

std::thread t1([&counterMutex, iterations]() {
for (int i = 0; i < iterations; ++i) counterMutex.increment();
});
std::thread t2([&counterMutex, iterations]() {
for (int i = 0; i < iterations; ++i) counterMutex.increment();
});

t1.join();
t2.join();

auto end1 = std::chrono::high_resolution_clock::now();
auto time1 = std::chrono::duration_cast<std::chrono::microseconds>(end1 start1);

// 测试 atomic
CounterWithAtomic counterAtomic;
auto start2 = std::chrono::high_resolution_clock::now();

std::thread t3([&counterAtomic, iterations]() {
for (int i = 0; i < iterations; ++i) counterAtomic.increment();
});
std::thread t4([&counterAtomic, iterations]() {
for (int i = 0; i < iterations; ++i) counterAtomic.increment();
});

t3.join();
t4.join();

auto end2 = std::chrono::high_resolution_clock::now();
auto time2 = std::chrono::duration_cast<std::chrono::microseconds>(end2 start2);

std::cout << "Mutex time: " << time1.count() << " μs\\n";
std::cout << "Atomic time: " << time2.count() << " μs\\n";
std::cout << "Speedup: " << (double)time1.count() / time2.count() << "x\\n";
}

atomic 的限制

1. 不是所有类型都支持

// ✓ 支持的类型
std::atomic<int> a1;
std::atomic<bool> a2;
std::atomic<float> a3;
std::atomic<int*> a4;

// ✗ 不支持复杂类型
// std::atomic<std::vector<int>> a5; // 编译错误
// std::atomic<std::string> a6; // 编译错误

// 但可以使用 is_lock_free 检查
std::atomic<int> x;
if (x.is_lock_free()) {
std::cout << "int is lock-free\\n";
} else {
std::cout << "int uses locks\\n";
}

2. 不能保证整个代码块的原子性

std::atomic<int> value(0);

// ❌ 这不是原子的
if (value > 0) {
// 在这里,其他线程可能修改 value
value; // 可能变成负数
}

// ✓ 正确做法:使用 CAS
int expected = value.load();
while (expected > 0 && !value.compare_exchange_weak(expected, expected 1)) {
// 循环重试
}

3. ABA 问题

#include <atomic>
#include <iostream>
#include <thread>

std::atomic<int*> ptr(nullptr);

void abaProblem() {
int* x = new int(1);
int* y = new int(2);

ptr.store(x);

std::thread t1([&]() {
int* p = ptr.load();
int* next = p ? new int(*p + 1) : nullptr;
// ABA 问题:p 可能被其他线程修改后又改回来
if (ptr.compare_exchange_weak(p, next)) {
std::cout << "CAS succeeded\\n";
} else {
std::cout << "CAS failed\\n";
delete next;
}
});

std::thread t2([&]() {
int* p = ptr.load();
delete p;
ptr.store(y);
// 又改回 x
ptr.store(new int(1));
});

t1.join();
t2.join();
}

实际应用场景

1. 引用计数

#include <atomic>
#include <iostream>

class SharedObject {
private:
std::atomic<int> refCount{1};

public:
void addRef() {
refCount.fetch_add(1, std::memory_order_relaxed);
}

void release() {
if (refCount.fetch_sub(1, std::memory_order_acq_rel) == 1) {
delete this;
}
}

int getRefCount() const {
return refCount.load(std::memory_order_relaxed);
}

virtual ~SharedObject() {
std::cout << "Object destroyed\\n";
}
};

void referenceCountingExample() {
SharedObject* obj = new SharedObject();
std::cout << "Initial ref count: " << obj->getRefCount() << "\\n";

obj->addRef();
std::cout << "After addRef: " << obj->getRefCount() << "\\n";

obj->release();
std::cout << "After release: " << obj->getRefCount() << "\\n";

obj->release(); // 对象被销毁
}

2. 单例模式

#include <atomic>
#include <iostream>

class Singleton {
private:
static std::atomic<Singleton*> instance;
static std::mutex mtx;

Singleton() {
std::cout << "Singleton created\\n";
}

public:
static Singleton* getInstance() {
Singleton* tmp = instance.load(std::memory_order_acquire);
if (tmp == nullptr) {
std::lock_guard<std::mutex> lock(mtx);
tmp = instance.load(std::memory_order_relaxed);
if (tmp == nullptr) {
tmp = new Singleton();
instance.store(tmp, std::memory_order_release);
}
}
return tmp;
}

void doSomething() {
std::cout << "Doing something\\n";
}
};

std::atomic<Singleton*> Singleton::instance{nullptr};
std::mutex Singleton::mtx;

void singletonExample() {
Singleton* s1 = Singleton::getInstance();
Singleton* s2 = Singleton::getInstance();

std::cout << "s1 == s2: " << (s1 == s2) << "\\n";
s1->doSomething();
}

3. 自旋锁

#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)) {
// 自旋等待
}
}

void unlock() {
flag.clear(std::memory_order_release);
}
};

SpinLock spinLock;
int sharedData = 0;

void spinLockExample() {
auto worker = [](int id) {
for (int i = 0; i < 10000; ++i) {
spinLock.lock();
++sharedData;
spinLock.unlock();
}
};

std::thread t1(worker, 1);
std::thread t2(worker, 2);

t1.join();
t2.join();

std::cout << "Shared data: " << sharedData << "\\n";
}

总结

特性说明
原子性 操作不可分割,不会被中断
可见性 修改对其他线程立即可见
有序性 防止编译器和 CPU 重排序
无锁 通常比 mutex 性能更好
适用场景 简单的计数器、标志位、指针操作
限制 不支持复杂类型,不能保证代码块原子性

使用建议:

  • 简单操作优先使用 atomic
  • 复杂操作使用 mutex
  • 理解内存序,选择合适的内存序
  • 注意 ABA 问题
  • 使用 is_lock_free() 检查是否真正无锁
  • std::atomic 是 C++ 并发编程的基础工具,正确理解和使用它对于编写高效、安全的并发程序至关重要。

    赞(0)
    未经允许不得转载:171主机测评 » 如何理解 C++ 中的 atomic?
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址