C++ Memory Order

为什么需要Memory Order

  • 原子操作(简单语句,C++也不保证是原子操作)
  • 指令执行顺序(编译器可能优化代码使代码顺序发生变化,CPU可能会调整指令执行顺序)
  • CPU可见性(再CPU cache的影响下,可能存在一个CPU执行了某个指令,不会立即被其他CPU所见)
int64_t i = 0;
// Thread-1
i = 100;

// Thread2
std::cout << i;

上面的代码输出可能是一个未定义的行为,由于不同CPU结构对指令的处理也不同,有的CPU再处理64为字节时需要两条指令,那这样i就不是原子操作,Thread-1和Thread-2的行为就变成了未定义.

std::atomic x{0};
std::atomic y{0};

// Thread-1:
x.store(100, std::memory_order_relaxed);
y.store(200, std::memory_order_relaxed);

// Thread-2:
while (y.load(std::memory_order_relaxed) != 200)
    ;
std::cout << x.load(std::memory_order_relaxed);

上面的例子,线程1对x和y分别写入100,200,并且x, y的操作属于原子操作,线程2等待y变成200之后,输出x的值,在这里x的值可能是0,由于再多线程下并没有保证指令指令顺序的一致性,所以上面的顺序是两个字:任意,那y.strore就可能执行再x.stroe的前面,导致x输出0.

int x = 0;

// Thread-1:
x = 100;    // A

// Thread-2:
std::cout << x; // B

现在假设A在B之前执行,x的可能输出是多少?

x可能输出0,100,由于CPU可见性的问题,可能Thread-1中的A操作执行完,x的值还在高速缓存上,并没有更新到内存,之后Thread-2中的B去读取x的值就读取到了旧值.

以上三个例子分别对应的三种可能改变程序执行结果的行为(多线程下),C++11 memory order就是解决以上的问题,让C++用户写多线程程序时不需要考虑机器环境(这个时C++11 memory model功劳), 在C++11中可以提供了mutex,std::atomic…

C++11 std::atomic

c11 的顺序格式如下:

  • Relaxed ordering
  • Release_Acquire ordering
  • Release_Consume ordering
  • Sequentially-consistent ordering

分别对应了std::atomic中的顺序如下:

  • std::memory_order_relaxed
  • std::memory_order_acquire
  • std::memory_order_release
  • std::memory_order_consume
  • std::memory_acq_rel
  • std::memory_seq_cst

Relaxed ordering

std::atomic x = 0;
std::atomic y = 0;

// Thread-1:
r1 = y.load(memory_order_relaxed);  // A
x.store(r1, memory_order_relaxed);  // B

// Thread-2:
r2 = x.load(memory_order_relaxed);  // C
y.stroe(99, memory_order_relaxed);  // D

r1 == r2 == 99可能出现吗?

可能的, D->A->B->C的执行顺序就可能出现r1 == r2 == 99

解释: Relaxed ordering仅仅只保证atomic自带的方法是原子操作,除此之外,不提供任何跨线程的同步,由于不提供任何跨线程的同步,所以上面例子中的执行顺序就是两个字:任意,但是Relaxed ordering好处也非常明显,就是性能高.

Release-Acquire ordering

在这种模型下,load()采用memory_order_acquire, store()采用memory_order_release,提供了两个线程之间的主要功能.

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

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

线程之间由于Cpu cache,导致线程之间不可见,Release-Acquire提供了线程之间的可见.

std::atomic ready{false};
int data = 0;
void producer() {
    data = 10;                                      // A
    ready.store(true, std::memroy_order_release);   // B
}

void consumer() {
    while (!ready.load(std::memory_order_acquire))  // C
        ;
    assert(data == 100);                            // D
}

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

上面例子中assert永远不会触发,根据上面的指令限制规则,A不可能移动到B的后面,D不可能移动到C的前面.

std::atomic f1 = false;
std::atomic f2 = false;

// thread1
f1.store(true, memroy_order_release);   // A
if (!f2.load(memory_order_acquire)) {   // B
    // critical section
}

//thread2
f2.store(true, memroy_order_release);   // C
if (!f1.load(memory_order_acquire)) {   // D
    // critical section
}

上面例子中两个if条件可能同时满足,并且同时进入临界区,根据上面的指令规则,B可以移动到A的前面,D可以移动到C的前面,这都不违法规则.

Release-Consume ordering

Release-Consume相比Release-Acquire,在功能上弱了一点,它仅仅将依赖于x(假设)的值进行同步.

Sequential consisten ordering

Release-Acquire就同步一个x,顺序一致会对所有的变量的所有原子操作都同步,这样,所有的原子操作就跟由一个线程执行似的.

Original: https://www.cnblogs.com/gd-luojialin/p/15028134.html
Author: gd_沐辰
Title: C++ Memory Order

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/535276/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 【C++服务端技术】消息队列

    ThreadWorkUnit.h #pragma once #include #include #include "SafeQueue.h" namespace…

    C++ 2023年5月29日
    048
  • C++实现的各种排序算法

    提起排序算法相信大家都不陌生,或许很多人已经把它们记得滚瓜烂熟,甚至随时可以写出来。最近在学习这一块, 索性就把各种内部排序算法总结归纳了一下: 1、 算法分类: 十种常见排序算法…

    C++ 2023年5月29日
    047
  • c++以代理的方式来实现接口化编程

    假如你项目代码本身已经实现了很多的实体类,但并未采用接口,可以考虑以这种方式来实现接口化编程 struct ITest { virtual void Test()=0; }; cl…

    C++ 2023年5月29日
    067
  • C++应该更多使用堆还是栈?

    栈,是不需要涉及内存分配的,你可以把它看成一个很长的连续内存,用来执行函数。自动以先进后出的方式使用。具体的进出在C++里你可以假设是不能操纵这个栈的,实际上它存在。 _main函…

    C++ 2023年5月29日
    055
  • C++强大背后

    在31年前(1979年),一名刚获得博士学位的研究员,为了开发一个软件项目发明了一门新编程语言,该研究员名为Bjarne Stroustrup,该门语言则命名为——C with c…

    C++ 2023年5月29日
    055
  • C++/服务器开发4天实战训练营

    第一天: 1.四种不同的方式来实现add函数 //面向过程 int add1(int a, int b) { return a + b; } //面向对象 class ADD{ p…

    C++ 2023年5月29日
    042
  • 汉诺塔的c++实现

    void hanNuoTa(int n,int a,int b,int c) { if (n == 0) return; hanNuoTa(n – 1, a, c, b); cou…

    C++ 2023年5月29日
    059
  • C++ lambda 用法

    为什么要使用lambda 就地匿名的定义一个目标函数或者函数对象,不需要额外的再写一个命名函数或者函数对象,以更直接的方式去写函数,可以调高程序的可读性和可维护性。 简洁:不要额外…

    C++ 2023年5月29日
    063
  • [C++] 类的成员变量和成员方法

    类具有成员变量和成员方法 成员变量用来描述某个对象的具体特征,是静态的,也称为成员属性,这些属性一般设置为私有,仅供类的内部使用。 成员方法用来描述某个对象的具体行为,是动态的,也…

    C++ 2023年5月29日
    070
  • C++11 并发指南六(atomic 类型详解一 atomic_flag 介绍)

    C++11 并发指南已经写了 5 章,前五章重点介绍了多线程编程方面的内容,但大部分内容只涉及多线程、互斥量、条件变量和异步编程相关的 API,C++11 程序员完全可以不必知道这…

    C++ 2023年5月29日
    050
  • C++面试题1

    1,LeetCode给出一个 32 位的有符号整数,将这个整数中每位上的数字进行反转; 2,怎么判断一个变量是指针; Original: https://www.cnblogs.c…

    C++ 2023年5月29日
    053
  • JNI NDK (AndroidStudio+CMake )实现C C++调用Java代码流程

    JNI/NDK Java调用C/C++前言通过第三篇文章讲解在实际的开发过程中Java层调用C/C++层的处理流程。其实我们在很大的业务里也需要C/C+ +层去调用Java层,这两…

    C++ 2023年5月29日
    043
  • EclipseC++学习笔记-1 环境搭建

    最近一个项目使用的EclipseC++编写的,所以需要搭建一个EclipseC++平台开发。1、windows下载https://www.eclipse.org/downloads…

    C++ 2023年5月29日
    079
  • C++中的POD类型

    参考 定义 总结与理解 参考 https://en.cppreference.com/w/cpp/named_req/PODType 定义 知识的搬运工,以下内容抄的,虽然是硬性定…

    C++ 2023年5月29日
    064
  • C++快速入门系列教程

    移动开发工程师 。涉及 android、ios、jni Original: https://www.cnblogs.com/xitang/p/4176128.htmlAuthor:…

    C++ 2023年5月29日
    078
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球