iis服务器助手广告广告
返回顶部
首页 > 资讯 > 后端开发 > 其他教程 >c++ atomic原子编程中Memory Order的示例分析
  • 412
分享到

c++ atomic原子编程中Memory Order的示例分析

2023-06-15 09:06:09 412人浏览 安东尼
摘要

这篇文章给大家分享的是有关c++ atomic原子编程中Memory Order的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。概述但是,基于内核对象的同步,会带来昂贵的上下文切换(用户态切换到内核态,占

这篇文章给大家分享的是有关c++ atomic原子编程中Memory Order的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。

概述

但是,基于内核对象的同步,会带来昂贵的上下文切换(用户态切换到内核态,占用1000个以上的cpu周期)。就需要使用另一种方法 —— 原子指令。

c++ atomic原子编程中Memory Order的示例分析

仅靠原子技术实现不了对资源的访问控制,即使简单计数操作,看上去正确的代码也可能会crash。

这里的关键在于编译器和cpu实施的重排指令导致了读写顺序的变化。只要没有依赖,代码中在后面的指令就可能跑到前面去,编译器和CPU都会这么做。

注1:单线程代码不需要关心乱序的问题。因为乱序至少要保证这一原则:不能改变单线程程序的执行行为

注2:内核对象多线程编程在设计的时候都阻止了它们调用点中的乱序(已经隐式包含memory barrier),不需要考虑乱序的问题。

注3:使用用户模式下的线程同步时,乱序的效果才会显露无疑。

程序员可以使用c++11 atomic提供了6种memory order,来在编程语言层面对编译器和cpu实施的重排指令行为进行控制

c++ atomic原子编程中Memory Order的示例分析

多线程编程时,通过这些标志位,来读写原子变量,可以组合出4种同步模型:

Relaxed ordering

Release-Acquire ordering

Release-Consume ordering

Sequentially-consistent ordering

默认情况下,std::atomic使用的是Sequentially-consistent ordering(最严格的同步模型)。但在某些场景下,合理使用其它3种ordering,可以让编译器优化生成的代码,从而提高性能。

Relaxed ordering

在这种模型下,std::atomic的load()和store()都要带上memory_order_relaxed参数。Relaxed ordering仅仅保证load()和store()是原子操作,除此之外,不提供任何跨线程的同步。

先看看一个简单的例子:

std::atomic<int> x = 0;     // global variablestd::atomic<int> y = 0;     // global variable  Thread-1:                                  Thread-2:r1 = y.load(memory_order_relaxed); // A    r2 = x.load(memory_order_relaxed); // Cx.store(r1, memory_order_relaxed); // B    y.store(42, memory_order_relaxed); // D

执行完上面的程序,可能出现r1 == r2 == 42。理解这一点并不难,因为编译器允许调整 C 和 D 的执行顺序。

如果程序的执行顺序是 D -> A -> B -> C,那么就会出现r1 == r2 == 42。

如果某个操作只要求是原子操作,不需要其它同步的保障,就可以使用 Relaxed ordering。程序计数器是一种典型的应用场景。

#include <cassert>#include <vector>#include <iOStream>#include <thread>#include <atomic>std::atomic<int> cnt = {0};void f(){    for (int n = 0; n < 1000; ++n) {        cnt.fetch_add(1, std::memory_order_relaxed);    }}int main(){    std::vector<std::thread> v;    for (int n = 0; n < 10; ++n) {        v.emplace_back(f);    }    for (auto& t : v) {        t.join();    }    assert(cnt == 10000);    // never failed    return 0;}

Release-Acquire ordering

在这种模型下,store()使用memory_order_release,而load()使用memory_order_acquire。这种模型有两种效果,第一种是可以限制 CPU 指令的重排:

(1)在store()之前的所有读写操作,不允许被移动到这个store()的后面。 // write-release语义

(2)在load()之后的所有读写操作,不允许被移动到这个load()的前面。 // read-acquire语义

该模型可以保证:如果Thread-1的store()的那个值,成功被 Thread-2的load()到了,那么 Thread-1在store()之前对内存的所有写入操作,此时对 Thread-2 来说,都是可见的。

下面的例子阐述了这种模型的原理:

#include <thread>#include <atomic>#include <cassert>#include <string>std::atomic<bool> ready{ false };int data = 0;void producer(){    data = 100;                                       // A    ready.store(true, std::memory_order_release);     // B}void consumer(){    while (!ready.load(std::memory_order_acquire))    // C        ;    assert(data == 100); // never failed              // D}int main(){    std::thread t1(producer);    std::thread t2(consumer);    t1.join();    t2.join();    return 0;}

让我们分析一下这个过程:

首先 A 不允许被移动到 B 的后面。

同样 D 也不允许被移动到 C 的前面。

当 C 从 while 循环中退出了,说明 C 读取到了 B store()的那个值,此时,Thread-2 保证能够看见 Thread-1 执行 B 之前的所有写入操作(也即是 A)。

使用Release-Acquire ordering实现双重检查模式(DLCP)

下面单件为例来说明:

class Singleton{public:    static Singleton* get_instance() {        Singleton* tmp = instance_.load(std::memory_order_acquire);        if (tmp == nullptr) {            std::unique_lock<std::mutex> lk(mutex_);            tmp = instance_;            if (tmp == nullptr) {                tmp = new Singleton();                instance_.store(std::memory_order_release);            }        }        return tmp;    }private:    Singleton() = default;    static std::atomic<Singleton*> instance_;    static std::mutex mutex_;};

使用Release-Acquire ordering实现自旋锁(Spinlock)

获取和释放语义,是实现锁的基础(Spinlock, Mutex, RWLock, ...),所有被[Read Acquire,Write Release]包含的区域,即构成了一个临界区,临界区里的内存操作,不会乱序到临界区之外执行。

            read-acquire(判断是否加锁,没则加锁,否则循环等待)

-------------------------------------------------------------------------

            all memory operation stay between the line(临界区)

-------------------------------------------------------------------------

                        write-release(释放锁)

实现代码如下:

#include <atomic>class simple_spin_lock{public:    simple_spin_lock() = default;    void lock()    {        while (flag.test_and_set(std::memory_order_acquire))            continue;    }    void unlock()    {        flag.clear(std::memory_order_release);    }private:    simple_spin_lock(const simple_spin_lock&) = delete;    simple_spin_lock& operator =(const simple_spin_lock&) = delete;    std::atomic_flag flag = ATOMIC_FLAG_INIT;};

①对std::atomic_flag的操作具有原子性,保证了同一时间,只有一个线程能够lock成功,其余线程全部在while循环

②使用了acquire内存屏障, 所以lock具有获取语义

③使用了release内存屏障, 所以unlock具有释放语义

Release-Consume ordering

在这种模型下,store()使用memory_order_release,而load()使用memory_order_consume。这种模型有两种效果,第一种是可以限制 CPU 指令的重排:

(1)在store()之前的所有读写操作,不允许被移动到这个store()的后面。

(2)在load()之后的所有依赖此原子变量的读写操作,不允许被移动到这个load()的前面。

注:不依赖此原子变量的读写操作可能会CPU指令重排

下面的例子阐述了这种模型的原理:

#include <thread>#include <atomic>#include <cassert>#include <string>std::atomic<std::string*> ptr;int data;// thread1void producer(){    std::string* p  = new std::string("Hello"); // A    data = 42; // B    ptr.store(p, std::memory_order_release); // C}// thread2void consumer(){    std::string* p2;    while (!(p2 = ptr.load(std::memory_order_consume))) // D        ;    assert(*p2 == "Hello"); //E     always true: *p2 carries dependency from ptr    assert(data == 42); // F     may be false: data does not carry dependency from ptr}int main(){    std::thread t1(producer);    std::thread t2(consumer);    t1.join();     t2.join();    return 0;}

Sequentially-consistent ordering

所有以memory_order_seq_cst为参数的原子操作(不限于同一个原子变量),对所有线程来说有一个全局顺序(total order)

并且两个相邻memory_order_seq_cst原子操作之间的其他操作(包括非原子变量操作),不能reorder到这两个相邻操作之外

UE4下的Memory Order

enum class EMemoryOrder{    // Provides no guarantees that the operation will be ordered relative to any other operation.    Relaxed,    // Establishes a single total order of all other atomic operations marked with this.    SequentiallyConsistent  // Load和Store函数缺省为该类型};

详见:UnrealEngine\Engine\Source\Runtime\Core\Public\Templates\Atomic.h

Atomic相关的测试代码见:UnrealEngine\Engine\Source\Runtime\Core\Private\Tests\Misc\AtomicTest.cpp

c++ atomic原子编程中Memory Order的示例分析

感谢各位的阅读!关于“c++ atomic原子编程中Memory Order的示例分析”这篇文章就分享到这里了,希望以上内容可以对大家有一定的帮助,让大家可以学到更多知识,如果觉得文章不错,可以把它分享出去让更多的人看到吧!

--结束END--

本文标题: c++ atomic原子编程中Memory Order的示例分析

本文链接: https://www.lsjlt.com/news/279347.html(转载时请注明来源链接)

有问题或投稿请发送至: 邮箱/279061341@qq.com    QQ/279061341

本篇文章演示代码以及资料文档资料下载

下载Word文档到电脑,方便收藏和打印~

下载Word文档
猜你喜欢
  • c++ atomic原子编程中Memory Order的示例分析
    这篇文章给大家分享的是有关c++ atomic原子编程中Memory Order的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。概述但是,基于内核对象的同步,会带来昂贵的上下文切换(用户态切换到内核态,占...
    99+
    2023-06-15
  • 详解c++ atomic原子编程中的Memory Order
    目录概述Relaxed orderingRelease-Acquire ordering使用Release-Acquire ordering实现双重检查锁模式(DLCP)使用Rele...
    99+
    2024-04-02
  • Java多线程Atomic包操作原子变量与原子类的示例分析
    这篇文章主要介绍Java多线程Atomic包操作原子变量与原子类的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!一、何谓Atomic?Atomic一词跟原子有点关系,后者曾被人认为是最小物质的单位。计算机中的...
    99+
    2023-05-30
    java
  • c#中异步编程的示例分析
    这篇文章给大家分享的是有关c#中异步编程的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。一、什么算异步?  广义来讲,两个工作流能同时进行就算异步,例如,CPU与外设之间的工作流就是异步的。在面向服务的系...
    99+
    2023-06-14
  • C++模板编程的示例分析
    这篇文章主要为大家展示了“C++模板编程的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“C++模板编程的示例分析”这篇文章吧。模板初阶泛型编程在计算机程序设计领域,为了避免因数据类型的不...
    99+
    2023-06-25
  • C#异步编程的示例分析
    小编给大家分享一下C#异步编程的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!异步编程在处理并发方面被使用的越来越多,之所以说上面一句话,是为了区分多线程...
    99+
    2023-06-17
  • Oracle OM 中Order工作流的示例分析
    这篇文章将为大家详细讲解有关Oracle OM 中Order工作流的示例分析,文章内容质量较高,因此小编分享给大家做个参考,希望大家阅读完这篇文章后对相关知识有一定的了解。Oracle OM 的setup工...
    99+
    2024-04-02
  • C#中管道式编的示例分析
    这篇文章主要介绍C#中管道式编的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!前言在 C# 编程中,管道式编程(Pipeline Style programming)其实存在已久,最明显的就是我们经常使用的 ...
    99+
    2023-06-20
  • java中力求故障原子性的示例分析
    这篇文章给大家分享的是有关java中力求故障原子性的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。力求故障原子性第 8 项是关于失败的。一般规则是失败的方法不应该改变方法中对象的状态。为了尽早失败,一种方...
    99+
    2023-06-04
  • PHP中SOCKET编程的示例分析
    这篇文章主要介绍了PHP中SOCKET编程的示例分析,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。1. 预备知识一直以来很少看到有多少人使用php的socket模块来做一些事...
    99+
    2023-06-15
  • base64编码原理的示例分析
    这篇文章将为大家详细讲解有关base64编码原理的示例分析,小编觉得挺实用的,因此分享给大家做个参考,希望大家阅读完这篇文章后可以有所收获。 base64编码原理 最近在做将文件转成base64编码,并...
    99+
    2024-04-02
  • C++中string底层原理的示例分析
    小编给大家分享一下C++中string底层原理的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!一、深浅拷贝浅拷贝:在实现string时要是不实先strin...
    99+
    2023-06-25
  • udp编程的示例分析
    小编给大家分享一下udp编程的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!什么是UDPUDP是User Datagram Protocol(用户数据报协...
    99+
    2023-06-27
  • C语言中程序编译系统的示例分析
    这篇文章主要介绍C语言中程序编译系统的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!程序的翻译环境和执行环境在ANSI C的任何一种实现中,存在两个不同的环境 :第1种是翻译环境,在这个环境中源代码被转换为可...
    99+
    2023-06-29
  • CSS中的层分离编程示例分析
    本篇文章为大家展示了CSS中的层分离编程示例分析,内容简明扼要并且容易理解,绝对能使你眼前一亮,通过这篇文章的详细介绍希望你能有所收获。随着CSS的发展,使用CSS有语义化的命名约定和CSS层的分离,将有助...
    99+
    2024-04-02
  • C++内嵌汇编的示例分析
    这篇文章主要为大家展示了“C++内嵌汇编的示例分析”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“C++内嵌汇编的示例分析”这篇文章吧。汇编语言汇编语言是一种功能很强的程序设计语言,也是利用了计算...
    99+
    2023-06-28
  • Promise中异步编程的示例分析
    这篇文章主要介绍Promise中异步编程的示例分析,文中介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们一定要看完!实例如下所示://1.解决异步回调问题 //1.1 如何同步异步请求 //如...
    99+
    2024-04-02
  • JavaScript中异步编程的示例分析
    这篇文章给大家分享的是有关JavaScript中异步编程的示例分析的内容。小编觉得挺实用的,因此分享给大家做个参考,一起跟随小编过来看看吧。目的提升开发效率,编写易维护的代码引子问题请求时候为什么页面卡死??$.ajax({ &n...
    99+
    2023-06-15
  • Go1.18中泛型编程的示例分析
    小编给大家分享一下Go1.18中泛型编程的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!前言经过这几年的千呼万唤,简洁的Go语言终于在1.18版本迎来泛型...
    99+
    2023-06-22
  • C++程序的示例分析
    小编给大家分享一下C++程序的示例分析,相信大部分人都还不怎么了解,因此分享这篇文章给大家参考一下,希望大家阅读完这篇文章后大有收获,下面让我们一起去了解一下吧!我们先来看一段C++的示例代码:// my first&n...
    99+
    2023-06-25
软考高级职称资格查询
编程网,编程工程师的家园,是目前国内优秀的开源技术社区之一,形成了由开源软件库、代码分享、资讯、协作翻译、讨论区和博客等几大频道内容,为IT开发者提供了一个发现、使用、并交流开源技术的平台。
  • 官方手机版

  • 微信公众号

  • 商务合作