首页 C++内存模型
文章
取消

C++内存模型

C++11的内存模型是一种规定了多线程并发操作如何交互的规则。这个模型主要由两部分组成:原子操作(atomic operations)和内存顺序(memory orders)。

原子操作

原子操作是一种特殊的操作,它们在执行期间不能被中断。换句话说,原子操作在执行过程中不会被其他线程的操作所影响。C++11为各种基础类型(如intfloatdouble,指针等)提供了原子版本,它们都在std::atomic命名空间下。

例如:

1
std::atomic<int> atomic_counter;

std::atomic提供了一系列的操作,比如load(), store(), fetch_add(), fetch_sub()等,它们都是原子的。

内存顺序

内存顺序定义了原子操作的执行顺序。C++11提供了以下几种内存顺序:

  • std::memory_order_relaxed:不强制执行任何顺序,只保证了单个线程中的操作顺序。
  • std::memory_order_consume:保证了本线程中,所有依赖于当前原子操作的后续操作,都不会在当前操作之前执行。
  • std::memory_order_acquire:标记读。保证了本线程中,所有在当前原子读操作之后的读写操作,都不会在当前操作之前执行。
  • std::memory_order_release:标记写。保证了本线程中,所有在当前原子写操作之前的读写操作,都不会在当前操作之后执行。
  • std::memory_order_acq_rel:同时具有acquirerelease的效果。
  • std::memory_order_seq_cst:全序,即在所有线程中都按照一致的顺序看待原子操作。

这些内存顺序对于控制线程间的同步非常有用。例如,使用std::memory_order_acquirestd::memory_order_release可以确保数据在被一个线程写入后,再被另一个线程读取。

示例

以下是一个使用std::memory_order的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
std::atomic<bool> data_ready(false);
std::array<int, 400> data;

void reader_thread() {
    while (!data_ready.load(std::memory_order_acquire)) {
        std::this_thread::sleep_for(std::chrono::milliseconds(1));
    }
    std::cout << "The answer=" << data[0] << "\\n";
}

void writer_thread() {
    data[0] = 42;
    data_ready.store(true, std::memory_order_release);
}

在这个例子中,std::memory_order_acquirestd::memory_order_release一起使用,确保了数据的正确同步。

这个模型的一个主要优点是,它提供了更精细的控制,以及可能的性能优化。然而,它也增加了编程的复杂性,需要更深入的理解以避免错误。

CAS操作和内存屏障

CAS(Compare And Swap)原子操作是一种常用的无锁同步策略。CAS原子操作包含三个操作数 —— 内存位置V、预期原值A和新值B。如果内存位置V的值与预期原值A相匹配,那么处理器会自动将该位置的值更新为新值B,否则,处理器不做任何操作。整个比较和替换的过程是一个原子操作。

使用示例:cpp里compare_exchange_weak方法执行CAS操作:如果value的当前值等于expected,那么就将value的值更新为updated,否则,就不做任何操作。参考:C++ 原子操作CAS和lockless无锁队列_c++ cas_雪夹雨夹雪的博客-CSDN博客

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <atomic>

class AtomicInteger {
private:
    std::atomic<int> value;

public:
    AtomicInteger(int initial_value) : value(initial_value) {}

    int increment() {
        int expected, updated;
        do {
            expected = value.load();
            updated = expected + 1;
        } while (!value.compare_exchange_weak(expected, updated));
        return updated;
    }

    int get() {
        return value.load();
    }
};

乱序执行(Out-of-Order Execution)是现代CPU采用的一种提高指令执行效率的技术。在乱序执行中,CPU会预先执行一些指令,然后再按照程序的顺序提交执行结果。这可能会导致多线程程序中的数据竞争问题。为了避免这个问题,我们可以使用内存屏障(Memory Barrier)或者原子操作来确保指令的执行顺序。内存屏障通常用于同步原语(如互斥锁)的实现中,以确保正确的内存可见性。例如,在C++11中,std::atomic的成员函数std::atomic::storestd::atomic::load就包含了内存屏障。

以下是一个使用了内存屏障的简单例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <atomic>
#include <thread>

std::atomic<bool> ready(false);
std::atomic<int> data(0);

void producer() {
    data.store(42, std::memory_order_release);
    ready.store(true, std::memory_order_release);
}

void consumer() {
    while (!ready.load(std::memory_order_acquire)) ;
    assert(data.load(std::memory_order_acquire) == 42);
}

int main() {
    std::thread t1(producer);
    std::thread t2(consumer);
    t1.join();
    t2.join();
    return 0;
}

在这个例子中,std::memory_order_release内存屏障确保了data的赋值操作在ready的赋值操作之前对其他线程可见。std::memory_order_acquire内存屏障确保了在看到ready变为true之后,可以看到data的正确值。

本文由作者按照 CC BY 4.0 进行授权

C++ NEW FEATURE

C++魔法汇总