C++ 并发编程:C++ 释放序列

探讨了C++中原子变量的使用,特别是在多线程环境下如何正确地使用原子操作来避免竞态条件。分析了一个具体的代码示例,解释了释放序列的概念,以及如何通过适当的内存顺序标记来确保线程间的正确同步。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一、抛出问题

以下代码是否会出现竞争问题?

#include <atomic>
#include <thread>

std::vector<int> queue_data;
std::atomic<int> count;

void populate_queue()
{
    unsigned const number_of_items=20;
    queue_data.clear();
    for(unsigned i=0;i<number_of_items;++i)
    {
        queue_data.push_back(i);
    }
    
    count.store(number_of_items,std::memory_order_release);//初始化存储
}

void consume_queue_items()
{
    while(true)
    {
        int item_index;
        if((item_index=count.fetch_sub(1,std::memory_order_acquire))<=0)//一个读-改-写操作
        {
            wait_for_more_items();
            continue;
        }
        process(queue_data[item_index-1]);
    }
}

int main()
{
    std::thread a(populate_queue);
    std::thread b(consume_queue_items);
    std::thread c(consume_queue_items);
    a.join();
    b.join();
    c.join();
}

二、C++ 释放序列

1、定义

  • C++标准定义
    在原子对象 M 上进行的释放操作 A 之后,由下列内容组成的 M 修改顺序的最长相接子序列
  1. 与进行 A 的同一线程所进行的写入(C++20 前)
  2. 任何线程对于 M 的原子的读修改写操作
    被称为 A 所引领的释放序列。
  • 通俗的说:当存储操作被标记为memory_order_release, memory_order_acq_rel或memory_order_seq_cst, 加载被标记为memory_order_consum, memory_order_acquire或memory_order_sqy_cst, 并且操作链上的每一加载操作都会读取之前操作写入的值, 因此链
    上的操作构成了一个释放序列(release sequence), 并且初始化存储同步(对应memory_order_acquire或memory_order_seq_cst)或是前序依赖(对应memory_order_consume)的最终加载。 操作链上的任何原子“读-改-写”操作可以拥有任意个存储序列(甚至是memory_order_relaxed)。

2、上面例子分析

  • 可能的问题
    当有两个读取线程时, 第二个fetch_sub()操作将看到被第一个线程修改的值, 且没有值通过store写入其中。 先不管释放序列的规则, 这里第二个线程与第一个线程不存在先行关系, 并且其对共享缓存中值的读取也不安全, 除非第一个fetch_sub()是带有memory_order_release语义的, 这个语义为两个消费者线程间建立了不必要的同步。 无论是释放序列的规则, 还是带有memory_order_release语义的fetch_sub操作, 第二个消费者看到的是一个空的queue_data, 无法从其获取任何数据, 并且这里还会产生条件竞争。
  • 分析结果
    幸运的是, 第一个fetch_sub()对释放顺序做了一些事情, 所以store()能同步与第二个fetch_sub()操作。 这里, 两个消费者线程间不需要同步关系。 这个过程在下图中展示, 其中虚线表示的就是释放顺序, 实线表示的是先行关系.
    在这里插入图片描述
    操作链中可以有任意数量的链接, 但是提供的都是“读-改-写”操作, 比如fetch_sub(),则store() 仍将与每一个使用memory_order_acquire语义的操作进行同步。 在这里例子中, 所有链接都是一样的, 并且都是获取操作, 但它们可由不同内存序列语义组成的操作混合。(也就是不是单纯的获取操作)
  • 结论
    有多个线程, 即使有(有序的)多个“读-改-写”操作(所有操作都已经做了适当的标记)在存储和加载操作之间, 依旧可以获取原子变量存储与加载的同步关系。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值