std::memory_order

当涉及到多线程共享数据的访问和修改时,考虑到数据一致性、性能优化和程序逻辑的正确性,就需要设置内存顺序标志。C++11引入了枚举类型std::memory_order,用于指定原子操作的内存顺序约束。


一、为什么要设置内存顺序?

在多线程程序中,不设置明确的内存顺序标志(或者使用不当的内存顺序标志),会导致线程间对内存操作的不可见性。主要原因如下:

  1. 编译器优化
    编译器为了提高程序性能,可能会重排序指令。如果不通过内存顺序标志明确禁止,编译器可能会将写操作重排序到读操作之后,或者反过来,这可能会导致一个线程看不到另一个线程对共享变量的更新。

  2. 处理器优化
    现代处理器通常采用复杂的执行策略,包括乱序执行(out-of-order execution)和内存访问优化,以提高执行效率。这些优化可能导致在没有明确内存顺序要求的情况下,内存操作的实际执行顺序与程序代码中的顺序不一致。

  3. 缓存一致性
    在多核处理器系统中,每个处理器核心可能有自己的缓存。如果不适当地使用内存顺序标志,一个核心上的线程对共享数据的修改可能只在该核心的缓存中可见,而不是立即写回主内存或对其他核心上的缓存可见。这会导致其他线程看到过时的数据。

使用原子操作和适当的内存顺序标志可以确保数据的一致性、避免数据竞争,并提升程序的性能。

二、std::memory_order定义和分类

头文件: <atomic>
定义:

typedef enum memory_order {
    memory_order_relaxed,
    memory_order_consume,
    memory_order_acquire,
    memory_order_release,
    memory_order_acq_rel,
    memory_order_seq_cst
} memory_order;

根据上述定义,内存顺序的设置分为如下几种模式:

  1. std::memory_order_relaxed:放宽模式,不对执行顺序做出任何保证,仅保证原子操作的原子性。
  2. std::memory_order_consume:(在C++17中已被弃用)理论上,它只保证依赖于被修改的原子变量的操作的顺序,但实际上,它通常被实现为与std::memory_order_acquire相同。
  3. std::memory_order_acquire:获取模式,保证在本原子操作读取或修改变量之前,本线程中的所有后续读写操作不会被重排序到本原子操作之前。
  4. std::memory_order_release:释放模式,保证在本原子操作写入变量之后,本线程中的所有先前的读写操作不会被重排序到本原子操作之后。
  5. std::memory_order_acq_rel:获取-释放模式,结合了acquire和release的保证,适用于同时具有读写需求的原子操作。
  6. std::memory_order_seq_cst:顺序一致模式,提供了最强的顺序保证。所有线程看到的操作顺序一致,所有seq_cst操作看上去就像是按照某种全局顺序执行的,这也是默认的内存顺序。

选择合适的内存顺序对于编写正确的多线程程序非常重要,因为不同的内存顺序会影响性能和正确性。std::memory_order_seq_cst虽然提供了最简单的编程模型,但通常也是性能最低的选项。在实际应用中,开发者需要根据具体情况权衡使用更轻量级的内存顺序约束以获得更好的性能。

三、 示例代码

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

std::atomic<bool> data_ready(false); // 共享原子标志
int data; // 假设这是要共享的数据

// 生产者线程函数
void producer() {
    data = 42; // 生产数据
    data_ready.store(true, std::memory_order_release); // 释放语义,确保data写入在此之前完成
}

// 消费者线程函数
void consumer() {
    while (!data_ready.load(std::memory_order_acquire)) {
        // 等待数据准备好,获取语义,确保对data的读取看到的是生产者线程中的写入
    }
    std::cout << "Data received: " << data << std::endl; // 数据已准备好,进行消费
}

int main() {
    std::thread t1(producer);
    std::thread t2(consumer);

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

    return 0;
}

在这个例子中,data_ready是一个原子标志,用来在生产者和消费者之间同步数据是否已经准备好。生产者在生产数据后将data_ready标志设置为true,使用std::memory_order_release以确保data的写入操作对消费者可见。消费者在检测到data_ready为true之前不断轮询这个标志,使用std::memory_order_acquire以确保它看到的是生产者线程中对data的最新写入。

  • 26
    点赞
  • 20
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值