技术思考25: 什么是C++内存模型?

智数追月鹤
• 阅读 62

1 内存模型说明了编译器在访问内存时可以做什么不能做什么

在c++98/03的世界中, 认为机器是单线程的,
c++规范中没有内存原子性操作的说明,
在那个时代, 编写多线程程序依赖具体的系统(如pthread, windows),
多线程程序是无法满足移植性的要求的.

在c++11中, 标准开始定义内存模型,
内存模型说明了编译器在访问内存时, 可以做什么, 不能做什么.

2 c++ 98标准下的多线程程序

如下是一段c++ 98标准下的多线程程序,
多线程依赖pthread

1.
#include <pthread.h>
2.#include <iostream>
3.
4.// 全局变量
5.int x, y;
6.
7.// 线程函数原型
8.void* thread1(void*);
9.void* thread2(void*);
10.
11.// 线程1设置x和y的值
12.void* thread1(void*) {
13.    x = 17;
14.    y = 37;
15.    return NULL;
16.}
17.
18.// 线程2打印x和y的值
19.void* thread2(void*) {
20.    std::cout << "x: " << x << ", y: " << y << std::endl;
21.    return NULL;
22.}
23.
24.int main() {
25.    // 创建线程1
26.    pthread_t tid1;
27.    pthread_create(&tid1, NULL, thread1, NULL);
28.
29.    // 创建线程2
30.    pthread_t tid2;
31.    pthread_create(&tid2, NULL, thread2, NULL);
32.
33.    // 等待线程1完成
34.    pthread_join(tid1, NULL);
35.    
36.    // 等待线程2完成
37.    pthread_join(tid2, NULL);
38.
39.    return 0;
40.}

3 c++11标准下的实现

下面是c++11标准下, 使用原子store/load来访问内存

1.
#include <iostream>
2.#include <thread>
3.#include <atomic>
4.
5.// 全局原子变量
6.std::atomic<int> x, y;
7.
8.// 线程函数
9.void thread1() {
10.    x.store(17);  // 设置x
11.    y.store(37);  // 设置y
12.}
13.
14.void thread2() {
15.    int x_value = x.load();  // 获取x
16.    int y_value = y.load();  // 获取y
17.    std::cout << "x: " << x_value << ", y: " << y_value << std::endl;
18.}
19.
20.int main() {
21.    // 创建线程1
22.    std::thread t1(thread1);
23.
24.    // 创建线程2
25.    std::thread t2(thread2);
26.
27.    // 等待线程1完成
28.    t1.join();
29.
30.    // 等待线程2完成
31.    t2.join();
32.
33.    return 0;
34.}

上面的程序可能打印0 0, 37 17, 0 17,
但是它不能打印的是 37 0 ,因为 C++ 11 中原子加载/存储的默认模式是强制顺序一致性。这只是意味着所有加载和存储都必须按照在每个线程中编写它们的顺序发生,
而线程之间的操作按照系统安排的方式交错。
因此,atomic的默认行为既提供了原子性,也提供了加载和存储的排序。

4 提升程序性能 放弃线程内有序性

在现代cpu上, 确保每个线程的顺序一致性代价高昂,
编译器可能需要在每次访存操作时, 发出内存屏障,
如果你只需要原子性, 而不需要有序性,
也就是说, 你可以容忍线程内乱序执行的话,
可以采用如下方式访问内存

1.
    x.store(17, std::memory_order_relaxed);  // 设置x
2.    y.store(37, std::memory_order_relaxed);  // 设置y
3.
4.    int x_value = x.load(std::memory_order_relaxed);  // 获取x
5.int y_value = y.load(std::memory_order_relaxed);  // 获取y

其他更多关键字包括:
std::memory_order_acquire 读取内存排序
std::memory_order_release 写入内存排序

5 c++中的atomic等效于java的volatile关键字

与java的内存模型比较,
c++中的atomic等效于java的volatile关键字

点赞
收藏
评论区
推荐文章
Wesley13 Wesley13
3年前
java之jvm
1.JVM内存模型_线程独占:栈,本地方法栈,程序计数器线程共享:堆,方法区_回答以上问题是需回答两个要点:1\.各部分功能2\.是否是线程共享2.JMM与内存可见性JMM是定义程序中变量的访问规则,线程对于变量的操作只能在自己的工作内存中进行,而不能直接对主内存操作.由于指令重排序,读写的顺序会被打乱,因此JMM需要
Wesley13 Wesley13
3年前
java并发编程实战:第十六章
一、什么是内存模型,为什么要使用它如果缺少同步,那么将会有许多因素使得线程无法立即甚至永远看到一个线程的操作结果编译器把变量保存在本地寄存器而不是内存中编译器中生成的指令顺序,可以与源代码中的顺序不同处理器采用乱序或并行的方式来执行指令保存在处理器本地缓存中的值,对于其他处理器是不可见在单线程中,只要
Wesley13 Wesley13
3年前
Java 内存模型
什么是Java内存模型?JMM(JavaMemoryModel,Java内存模型),它定义了多线程访问Java内存的规范。简单的说有以下几部分内容:Java内存模型将内存分为主内存和工作内存定义了几个原子操作,用于操作主内存和工作内存中的变量定义了volatile变量的使用规则happensbefor
Wesley13 Wesley13
3年前
Java内存溢出和内存泄露后怎么解决
1.首先这里先说一下内存溢出和内存泄露的区别:内存溢出outofmemory,是指程序在申请内存时,没有足够的内存空间供其使用,出现outofmemory;比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出。内存泄露memoryleak,是指程序在申请内存后,无法释放已申请的内存空间,一次内存泄露危害可以忽略,
Wesley13 Wesley13
3年前
Java多线程(二)
\恢复内容开始一,volatile关键字当多个线程操作共享数据时,可以保证内存中的数据可见性相较于synchronized关键字:1,不具备“互斥性”2,不能保证变量的原子性二,原子变量volatile保证内存可见性CAS(CompareAndSwap)算法保证数据的原子性内存值V预估值A更新值
Stella981 Stella981
3年前
Redis为什么使用单进程单线程方式也这么快
Redis采用的是基于内存的采用的是单进程单线程模型的KV数据库,由C语言编写。官方提供的数据是可以达到100000的qps。这个数据不比采用单进程多线程的同样基于内存的KV数据库Memcached差。Redis快的主要原因是:1.完全基于内存2.数据结构简单,对数据操作也简单3.使用多路I/O复用模型第一、二点不细讲,主要
Wesley13 Wesley13
3年前
Java多线程之内存可见性
Java多线程之内存可见性一、Java内存模型介绍什么是JMM?Java内存模型(JavaMemoryModel)描述了Java程序中各种变量(线程共享变量)的访问规则,以及在JVM中将变量存储到内存和从内存中读取出变量这样的细节所有的变量都存储在主内存中每个线程都
Wesley13 Wesley13
3年前
Java 多线程:volatile关键字
概念volatile也是多线程的解决方案之一。\\volatile能够保证可见性,但是不能保证原子性。\\它只能作用于变量,不能作用于方法。当一个变量被声明为volatile的时候,任何对该变量的读写都会绕过高速缓存,直接读取主内存的变量的值。如何理解直接读写主内存的值:回到多线程生成的原因(Java内存模型与
Wesley13 Wesley13
3年前
JAVA内存模型与线程以及volatile理解
Java内存模型是围绕在并发过程中如何处理原子性、可见性、有序性来建立的。一、主内存与工作内存  Java内存模型主要目标是在虚拟机中将变量存储到内存和从内存中取出变量。这里的变量包括:实例字段、静态字段、构成数组对象的元素;不包括局部变量和方法参数,因为它们是线程私有的。Java内存模型规定了所有变量都存储在主内存,线程的工作内
Wesley13 Wesley13
3年前
Java线程安全总结
浅谈java内存模型 不同的平台,内存模型是不一样的,但是jvm的内存模型规范是统一的。其实java的多线程并发问题最终都会反映在java的内存模型上,所谓线程安全无非是要控制多个线程对某个资源的有序访问或修改。总结java的内存模型,要解决两个主要的问题:可见性和有序性。我们都知道计算机有高速缓存的存在,处理器并不是每次处理数据都是取内
Wesley13 Wesley13
3年前
Java内存模型详解
内存模型(memorymodel)内存模型描述的是程序中各变量(实例域、静态域和数组元素)之间的关系,以及在实际计算机系统中将变量存储到内存和从内存取出变量这样的低层细节.不同平台间的处理器架构将直接影响内存模型的结构.在C或C中,可以利用不同操作平台下的内存模型来编写并发程序.但是,这带给开发人员的是,更高的学习成本.相