C++内存序、屏障和原子操作

news2024/11/26 6:21:58

文章目录

  • 一、原子类型
  • 二、原子操作函数
  • 三、内存序
    • 1)happens-before和synchronizes-with语义
    • 2)内存序模式
  • 四、标准库函数
  • 五、栅栏(Barrier)

一、原子类型

标准原子类型的备选名和与其相关的 std::atomic<> 特化类:

在这里插入图片描述

除上述类型外还有std::atomic<T*>类型,返回的也是T*类型,操作接口语义也是一样的。

同时原子类型也可能是自定义类型,如果是其它自定义类型,则需要满足下面一些条件:

  • 这个类型必须有拷贝赋值运算符;
  • 这个类型不能有任何虚函数或虚基类,以及必须使用编译器创建的拷贝赋值操作;
  • 自定义类型中所有的基类和非静态数据成员也都需要支持拷贝赋值操作,即可使用memcpy()进行拷贝;
  • 这个类型必须是“位可比的”(bitwise equality comparable),可以调用memcmp()对位进行比较;

但是建议自定义类型不要太过复杂,因为这样反而会降低程序性能。

以下是对应每种类型必须提供的成员接口函数:

在这里插入图片描述

二、原子操作函数

原子操作类常用成员函数有fetch_*、store、load、exchange、compare_exchange_weak和compare_exchange_strong:

  • fetch_*:先获取值再计算,即返回的是修改之前的值;
  • store:写入数据;
  • load:加载并返回数据;
  • exchange:直接设置一个新值;
  • compare_exchange_weak:先比较第一个参数的值和要修改的内存值(第二个参数)是否相等,如果相等才会修改,该函数有可能在except == value时也会返回false所以一般用在while中,直到为true才退出;
  • compare_exchange_strong:功能和*_weak一样,不过except == value时该函数保证不会返回false,但该函数性能不如*_weak;

注意:使用操作符(如+=、++、^=等)时要看类成员是否提供对应操作符,否则可能出现意想不到的问题。

三、内存序

1)happens-before和synchronizes-with语义

  • happens-before:

    如果两个操作之间存在依赖关系,并且一个操作一定比另一个操作先发生,那么者两个操作就存在happens-before关系;

  • synchronizes-with:

    synchronizes-with关系指原子类型之间的操作,如果原子操作A在像变量X写入一个之后,接着在同一线程或其它线程原子操作B又读取该值或重新写入一个值那么A和B之间就存在synchronizes-with关系;

注意这两中语义只是一种关系,并不是一种同步约束,也就是需要我们编程去保证,而不是它本身就存在。

2)内存序模式

C++内存序有如下六种标记:

  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;

具体含义如下:

  • memory_order_seq_cst:默认模式也是顺序要求最严格的一种模式,保证多个线程对同一个原子变量的访问,按照某种全局顺序进行,且不会出现重排序。
  • memory_order_release:释放内存序
    • 保证之前的所有写操作都在该操作之前完成,不能重排序,即保证happens-before关系。

    • 保证该操作之前写入的值对其它线程都是可见,这和写屏障功能很像:把CPU高速缓存中的数据同步到主存和其它CPU高速缓存中(其实是发送了一个更新指令消息到其它CPU的invalidate queue中),即该操作写入一个值后其它线程读取该值一定是之前写入的值,满足可见性。

    • 注意该模式只针对写,对读没有约束(即读可能会重排),一般和memory_order_acquire成对使用。

    • memory_order_release和memory_order_acquire常用用法(下面的2和3操作存在synchronizes-with关系):

#include <atomic>
#include <thread>
#include <assert.h>
std::atomic<bool> x,y;
std::atomic<int> z;
void write_x_then_y()
{
  x.store(true,std::memory_order_relaxed); // 1
  y.store(true,std::memory_order_release); // 2 保证写如的y对其它线程立马可见
}
void read_y_then_x()
{
  while(!y.load(std::memory_order_acquire)); // 3 保证能够读到y的最新值
  if(x.load(std::memory_order_relaxed)) // 4
    ++z;
}
int main()
{
  x=false;
  y=false;
  z=0;
  std::thread a(write_x_then_y);
  std::thread b(read_y_then_x);
  a.join();
  b.join();
  assert(z.load()!=0); // 5 这里z一定不会为0
}
  • memory_order_acquire:获取内存序
    • 保证之后的所有读操作都在该操作之后完成,不能重排序,即保证happens-before关系。
    • 保证该操作读取到的值一定是之前写入的值,这么读屏障功能很像:让store buffer和CPU高速缓存中的数据失效重主存中加载最新主句(其实是看invalidate queue中是否有该内存的更新指令,如果有重新重主存中加载最新的数据),即保证后面其它线程读取该值时能够读到最新值,满足可见性。
    • 注意该操作指针对读,对写没有约束(即写可能会重排),一般和memory_order_release成对使用。
  • memory_order_acq_rel:获取-释放内存序
    • 该操作和之前的写操作,该操作和之后的读操作,不能重排序,即保证happens-before关系。
    • 确保该原子操作之前的所有写操作在该原子操作之前都完成,并且该原子操作之后的所有读写操作都在该原子操作之后完成,即同时具有release和acquire内存序的功能。
    • 注意该操作对之前的读和之后的写没有约束。
  • memory_order_consume:消费内存序
    • 保证指针指向的原子对象指行原子读操作和之后的指针指向该原子操作的读是可见的,即满足happens-before关系,可理解为指针版的memory_order_acquire。

    • 如果一个线程在该原子操作之前执行了通过指针进行的读操作,那么该指针指向的对象在该原子操作在其它线程之后的读操作中一定是可见的。

    • 对指针进行的读操作产生约束,对其他类型的操作没有约束力。

    • memory_order_consume用法 :

struct X
{
  int i;
  std::string s;
};
std::atomic<X*> p;
std::atomic<int> a;
void create_x()
{
  X* x=new X;
  x->i=42;
  x->s="hello";
  a.store(99,std::memory_order_relaxed); // 1
  p.store(x,std::memory_order_release); // 2
}
void use_x()
{
  X* x;
  while(!(x=p.load(std::memory_order_consume))) // 3
  std::this_thread::sleep(std::chrono::microseconds(1));
  assert(x->i==42); // 4
  assert(x->s=="hello"); // 5
  assert(a.load(std::memory_order_relaxed)==99); // 6
}
int main()
{
  std::thread t1(create_x);
  std::thread t2(use_x);
  t1.join();
  t2.join();
}

有时我们为了提升性能对于memory_order_consume,我们可以使用 std::kill_dependecy()打破依赖关系提升性能,该函数会复制提供的参数给返回值:

int global_data[]={ … };
std::atomic<int> index;
void f()
{
  int i=index.load(std::memory_order_consume);
  do_something_with(global_data[std::kill_dependency(i)]);
}
  • memory_order_relaxed:松散的内存序,保证本线程中的原子变量满足happens-before关系,不能对其进行重排序,但是其它变量可以重排序,该约束对多线程之间没有要求,可能产生数据竞争。

四、标准库函数

C++标志也提供独立的(非类成员函数)原子操作函数如下:

  • std::atomic_load
  • std::atomic_store
  • std::atomic_exchange
  • std::atomic_compare_exchange_weak
  • std::atomic_compare_exchange_strong

这些函数和原子类成员函数功能一样,不过这些函数第一个参数传入的是原子类对象指针类型。同时以上接口还有*_explicit版本,这版接口最后一个参数支持传递memory order类型,如:std::atomic_store(&atomic_var,new_value) std::atomic_store_explicit(&atomic_var,new_value,std::memory_order_release )

std::atomic_load(&a)a.load()的作用一样,但需要注意的是,与a.load(std::memory_order_acquire)等价的操作是 std::atomic_load_explicit(&a, std::memory_order_acquire);

为什么会提供这类函数?

在C中只能使用指针,而不能使用引用,为了要与C语言兼容,例如:compare_exchange_weak()compare_exchange_strong()成员函数的第一个参数(期望值)是一个引用,而 std::atomic_compare_exchange_weak() (第一个参数是指向对象的指针)的第二个参数是一个指针。

标准库函数还支持std::shared_ptr<>智能指针类型,使用如下:

std::shared_ptr<my_data> p;
void process_global_data()
{
  std::shared_ptr<my_data> local=std::atomic_load(&p);
  process_data(local);
}
void update_global_data()
{
  std::shared_ptr<my_data> local(new my_data);
  std::atomic_store(&p,local);
}

五、栅栏(Barrier)

以上函数都是只针对原子变量,而栅栏是对所有数据有效。栅栏是一种同步原语,它可以用来同步多个线程之间的操作。栅栏可以将线程分为若干个阶段,在每个阶段中,线程需要等待其他线程完成特定的操作后才能继续执行下一步操作。栅栏的主要作用是协调多个线程之间的操作,确保它们按照预期的顺序执行。
在C++中,栅栏有多种实现,包括std::atomic_thread_fence、std::atomic_signal_fence、std::thread::join等。其中,std::atomic_thread_fence和std::atomic_signal_fence是用于控制内存访问顺序的栅栏,std::thread::join是用于等待其他线程完成的栅栏。
下面是std::atomic_thread_fence的用法示例:

#include <atomic>
#include <thread>
#include <assert.h>
std::atomic<bool> x,y;
std::atomic<int> z;
void write_x_then_y()
{
    x.store(true,std::memory_order_relaxed); // 1
    std::atomic_thread_fence(std::memory_order_release); // 2 栅栏也要指向如何限制内存序
    y.store(true,std::memory_order_relaxed); // 3 
}
void read_y_then_x()
{
    while(!y.load(std::memory_order_relaxed)); // 4
    std::atomic_thread_fence(std::memory_order_acquire); // 5 保证之的读操作能读到最新数据
    if(x.load(std::memory_order_relaxed)) // 6
      ++z;
}
void write_y_then_x()
{
    y.store(true, std::memory_order_relaxed);
    std::atomic_thread_fence(std::memory_order_release); // 7
    int r = x.load(std::memory_order_relaxed);
}

在使用栅栏的情况下原子变量就不需要指定内存序列了,可以改成memory_order_relaxed松散内存序。

注意栅栏的有效范围是前后紧邻的一行语句,如下代码中后面两行的语句顺序无法保证:

void write_x_then_y()
{
    std::atomic_thread_fence(std::memory_order_release);
    x.store(true,std::memory_order_relaxed);
    y.store(true,std::memory_order_relaxed);
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/616162.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

探索低代码的新形态(D2C、ChatGPT)

前言 低代码平台的出现&#xff0c;是互联网快速发展的背景下&#xff0c;满足产品快速迭代的实际需求。现在国内外都已经拥有非常多优秀的开源项目&#xff08;如&#xff1a;lowcode-engine&#xff09;和成熟的商业产品&#xff08;如&#xff1a;Mendix 、PowerPlatform&a…

Orillusion次时代 WebGPU 引擎

Orillusion 次时代 WebGPU 引擎 官网: https://www.orillusion.com/ 教程: https://www.orillusion.com/guide/ Orillusion 引擎是一款完全支持 WebGPU 标准的轻量级渲染引擎。基于最新的 Web 图形API标准&#xff0c;我们做了大量的探索和尝试&#xff0c;实现了很多曾经在 We…

python接口自动化(三)--如何设计接口测试用例(详解)

在开始接口测试之前&#xff0c;我们来想一下&#xff0c;如何进行接口测试的准备工作。或者说&#xff0c;接口测试的流程是什么&#xff1f;有些人就很好奇&#xff0c;接口测试要流程干嘛&#xff1f;不就是拿着接口文档直接利用接口 测试工具测试嘛。其实&#xff0c;如果…

【正点原子STM32连载】 第二十八章 低功耗实验摘自【正点原子】STM32F103 战舰开发指南V1.2

1&#xff09;实验平台&#xff1a;正点原子stm32f103战舰开发板V4 2&#xff09;平台购买地址&#xff1a;https://detail.tmall.com/item.htm?id609294757420 3&#xff09;全套实验源码手册视频下载地址&#xff1a; http://www.openedv.com/thread-340252-1-1.html 第二十…

计算机中数据的表示:定点数、浮点数

文章目录 1 概述2 定点数2.1 表示方法2.2 取值范围2.3 运算方法 3 浮点数3.1 表示方法3.2 运算方法 4 扩展4.1 等比数列前 n 项和公式 1 概述 #mermaid-svg-EXDrkn8G91FsDdps {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#merm…

给 a 标签设置 display:inline-block 之后 a 整体下沉

今天给一个a设置宽高&#xff0c;前提是添加了display:inline-block&#xff1b;然后发下a没有与父元素div顶部对齐&#xff0c;反而下沉了。试了好多办法都没成功&#xff0c;然后在网上找的教程。 原因 1、问题就是出在了display:inline-block;语句上&#xff0c;行内块元…

使用Python进行接口性能测试:从入门到高级

前言&#xff1a; 在今天的网络世界中&#xff0c;接口性能测试越来越重要。良好的接口性能可以确保我们的应用程序可以在各种网络条件下&#xff0c;保持流畅、稳定和高效。Python&#xff0c;作为一种广泛使用的编程语言&#xff0c;为进行接口性能测试提供了强大而灵活的工…

Redis:数据类型

一、Redis字符串(String) 1、String类型 String字符串&#xff1a;string类型是redis最基本、最简单的数据类型&#xff0c;一个key对应一个value。 String类型的二进制是安全的&#xff0c;可以包含任何数据&#xff0c;但是每一个value最大时512M 2、String命令 设置和获…

《人月神话》译文修订明细(6)-读者可以对照修改

《人月神话》译文修订明细&#xff08;1&#xff09;-读者可以对照修改 《人月神话》译文修订明细&#xff08;2&#xff09;-读者可以对照修改 《人月神话》译文修订明细&#xff08;3&#xff09;-读者可以对照修改 《人月神话》译文修订明细&#xff08;4&#xff09;-读…

前端面试题整理14

目录 1.什么是同步&#xff1f;什么是异步&#xff1f; 2.localStorage、sessionStorage和cookie的区别&#xff1f; 3.Vue中key的作用是什么&#xff1f; 4.支付流程是什么&#xff1f; 5.Vuex的模块化是如何做的&#xff1f; 6.Vite和webpack的不同&#xff1f;Vite的优…

BS LIS系统仪器数据采集方法

BS LIS系统仪器数据采集方法 BS LIS系统对检验仪器的数据采集主要通过串行口通讯、USB端口通讯、TCP/IP通讯、定时监控数据库和手工录入等几种方法。串行口通讯最为普遍&#xff0c;采用RS-232C标准&#xff0c;一般的仪器都支持此标准。定时监控数据库对仪器管理机上已有的检…

【Vue】Element Plus和Element UI中插槽使用

文章目录 前言一、两者的区别二、组件库三、具体讲解总结 前言 今天和大家讲一下Element Plus和Element UI这两个组件库中表格的插槽使用方法&#xff0c;一般情况下vue2使用Element UI这个组件库&#xff0c;表格组件的插槽的话一般都是使用v-slot&#xff0c;而vue3使用Elem…

如何进行有效的移动应用测试?10个步骤带你一战成神

移动应用的市场日益壮大&#xff0c;而随着这个市场的发展&#xff0c;如何有效地测试移动应用也成为了一个重要的问题。本文将为你提供一些关于如何进行有效的移动应用测试的建议&#xff0c;并提供一些实际测试例子。 1. 理解你的用户和使用场景 在进行移动应用测试之前&…

rror updating database. Cause: java.sql.SQLSyntaxErrorException解决方案

错误描述&#xff1a; ### Error updating database. Cause: java.sql.SQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near CONDITION 1 这里是因为字段名…

Linux多线程认识

目录 &#x1f427;一、什么是线程 1.1虚拟地址如何转换成物理地址 1.2多线程 1.3Linux进程vs线程 1.4从Linux内核和CPU的角度看线程 &#x1f427;二、Linux线程控制 2.1POSIX线程库 2.2线程异常 2.3线程终止 ①exit不可以用来终止线程 ②pthread_exit() ③pthread…

Revit干货|自动捕捉遇到困难?这份秘诀请收好!

在BIM行业里&#xff0c;Revit往往影响着我们的建模效率&#xff0c;尽管软件提供了许多功能&#xff0c;但在建模过程中还是会因繁琐的操作而浪费很多时间。 因此&#xff0c;在使用Revit建模时&#xff0c;我们需要掌握一些小技巧来提升效率&#xff0c;如快捷键的使用和工具…

早餐配送APP小程序开发 轻轻一点搞定营养早餐

早餐是一日三餐中最重要的一餐&#xff0c;需要营养添加。但是现在多数的年轻人因为快节奏的生活工作二忽视了早餐的重要性&#xff0c;没有时间做就对付几口很多人甚至不吃早餐。早餐预定配送APP小程序开发解决了上班族的早餐问题&#xff0c;不用排队到早餐店去挤着买豆浆油条…

Langchain学习笔记

Langchain学习笔记 1.环境2. 1.环境 1.创建虚拟环境,名叫langchain conda create -n langchain python conda activate langcahin pip install langchain pip install openai -i https://pypi.tuna.tsinghua.edu.cn/simple2.在jupyter中使用这个虚拟环境。 conda activate l…

DG4pros 1:500地籍精度免像控实验

前言 DG4pros是睿铂目前综合性能最强大的高端倾斜摄影相机&#xff0c;它完成了许多以前在业内人士认为难以做到或者不可能完成的项目。本期&#xff0c;我们实验的内容是1:500地籍精度的免相控作业。 DG4pros倾斜摄影相机 一.实验目的 本次实验共进行两组测试&#xff0c;分…

vue项目复制----复制一个项目为另一个项目仍然访问原来老项目代码

表现就如下边这张图&#xff0c;新项目名字叫pccs&#xff0c;旧项目名字叫vue-element-admin&#xff0c;能启动&#xff0c;运行成功&#xff0c;一切正常&#xff0c;但是你会发现仍然是老项目的。 解决办法&#xff1a;