c语言编程笔录

首页 > C语言 > c语言

c语言

浅谈关于C++memory_order的理解

更新时间:2023-08-09

前言

memory_order是C++中对于多线程编程中原子操作的内存序的控制机制。多线程编程中存在各种竞争条件和数据依赖关系,使用memory_order可以确保原子操作的有序性,保证多线程程序的正确性和一致性。本文将对C++的memory_order进行深入分析,介绍其各种内存序的含义和使用场景。

C++中的memory_order类型

在C++中,memory_order使用枚举类型表示,一共包含了9个枚举常量:

enum memory_order {
    memory_order_relaxed,       // 松散序
    memory_order_acquire,       // 获取序
    memory_order_release,       // 释放序
    memory_order_acq_rel,       // 获取和释放序
    memory_order_consume,       // 消耗序
    memory_order_seq_cst        // 顺序一致性序
};

下面分别对这些内存序进行详细解释。

各种内存序的含义和使用场景

1. memory_order_relaxed(松散序):

松散序没有任何同步制约,可以看作是没有任何内存序的默认内存序。对于没有数据依赖关系的原子操作,使用松散序可以获得最高的并行性。

示例代码:

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

// 线程1
void Thread1() {
    data.store(42, std::memory_order_relaxed);
    flag.store(1, std::memory_order_release);
}

// 线程2
void Thread2() {
    while (flag.load(std::memory_order_acquire) == 0)
        continue;
    std::cout << data.load(std::memory_order_relaxed) << std::endl;
}

在这个例子中,线程1对flag的存储使用了release内存序,线程2对flag的加载使用了acquire内存序,保证了对data的加载是合法的。

2. memory_order_acquire(获取序):

获取序保证了在原子操作之前的所有读操作都不会被重排序到原子操作之后。

示例代码:

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

// 线程1
void Thread1() {
    data = 42;
    std::atomic_thread_fence(std::memory_order_release);
    flag.store(1, std::memory_order_relaxed);
}

// 线程2
void Thread2() {
    while (flag.load(std::memory_order_acquire) == 0)
        continue;
    std::cout << data << std::endl;
}

在这个例子中,线程1使用release内存序将flag存储到主内存,而线程2对flag的加载使用了acquire内存序,保证了对data的加载不会发生在flag存储之前。

3. memory_order_release(释放序):

释放序保证了在原子操作之后的所有写操作都不会被重排序到原子操作之前。

示例代码:

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

// 线程1
void Thread1() {
    data = 42;
    std::atomic_thread_fence(std::memory_order_release);
    flag.store(1, std::memory_order_relaxed);
}

// 线程2
void Thread2() {
    while (flag.load(std::memory_order_relaxed) == 0)
        continue;
    std::atomic_thread_fence(std::memory_order_acquire);
    std::cout << data << std::endl;
}

在这个例子中,线程1使用release内存序将flag存储到主内存,而线程2在读取flag后使用acquire内存序,保证了对data的读取不会发生在flag存储之前。

4. memory_order_acq_rel(获取和释放序):

获取和释放序是获取序和释放序的合并,既保证了在原子操作之前的所有读操作都不会被重排序到原子操作之后,又保证了在原子操作之后的所有写操作都不会被重排序到原子操作之前。

示例代码:

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

// 线程1
void Thread1() {
    data = 42;
    std::atomic_thread_fence(std::memory_order_acq_rel);
    flag.store(1, std::memory_order_relaxed);
}

// 线程2
void Thread2() {
    while (flag.load(std::memory_order_relaxed) == 0)
        continue;
    std::atomic_thread_fence(std::memory_order_acq_rel);
    std::cout << data << std::endl;
}

线程1和线程2都使用了acq_rel内存序,保证了对data的读取不会发生在flag存储之前,并且对data的写操作不会发生在flag存储之后。

5. memory_order_consume(消耗序):

消耗序用于处理依赖关系,保证对依赖数据的读取不会发生在依赖关系建立之后的写操作之前。

示例代码:

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

// 线程1
void Thread1() {
    data = 42;
    std::atomic_thread_fence(std::memory_order_release);
    flag.store(1, std::memory_order_relaxed);
}

// 线程2
void Thread2() {
    while (flag.load(std::memory_order_consume) == 0)
        continue;
    std::cout << data << std::endl;
}

在这个例子中,线程1使用release内存序将flag存储到主内存,线程2使用consume内存序加载flag,保证了对data的加载不会发生在flag存储之后。

6. memory_order_seq_cst(顺序一致性序):

顺序一致性序是最强的内存序,既保证了原子操作前后的所有读写操作都不会被重排序,也保证了不同线程的原子操作的执行顺序是一致的。

示例代码:

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

// 线程1
void Thread1() {
    data = 42;
    flag.store(1, std::memory_order_seq_cst);
}

// 线程2
void Thread2() {
    while (flag.load(std::memory_order_seq_cst) == 0)
        continue;
    std::cout << data << std::endl;
}

在这个例子中,线程1对flag的存储和线程2对flag的加载都使用了seq_cst内存序,保证了对data的读取不会发生在flag存储之后,同时保证了线程1的原子操作先于线程2的原子操作。

总结

memory_order可以控制多线程编程中原子操作的内存序,保证了多线程程序的正确性和一致性。使用不同的内存序可以根据实际需求来控制原子操作的同步和可见性。松散序、获取序、释放序、获取和释放序、消耗序和顺序一致性序分别适用于不同的场景,开发者可以根据实际需求来选择合适的内存序。