理解内存序,指令重排与内存模型

简介: 理解内存序,指令重排与内存模型

1 内存序的问题

为了提高程序的效率,通常会对指令进行重排,也就是说代码在实际执行的时候与我们书写的顺序可能不同。

        通常有两种指令重排的方式:
        1  编译器优化重排
        2  cpu指令优化重排
    这种指令重排(重新排序),可能会引出一些非常难解的bug,因为从代码的表面看,不好分析出问题,所以必须要深入理解内存序。

2 从原子操作里的内存序

原子操作包含以下三个方面

        1 原子性:  锁ME状态
        2  同步性: 可见性 (不同线程看到的内存的值是一致的)
        3  顺序一致性 (代码执行顺序是不是跟我们写的代码顺序是一致的)
        其中的2 和 3 说的就是内存序。

3 内存序规定了什么?

假设核心0里有abcde 5个操作

            a,
            b, 
            c, 只有c是原子操作
            d,
            e
    内存序规定了多个线程访问同一个内存地址时的语义:同步性 与 顺序一致性

3.1 同步性

某个线程对内存地址的更新何时能被其它线程看见 (核1能否看核0里面b更新的最新值)

3.2 顺序一致性

某个线程对内存地址访问附近可以做怎么样的优化 (d, e是否可以优化到c前面?a,b能否优化到c后面来?)

4 内存模型,以C++为例

4.1 std::memory_order_relaxed

memory_order_relaxed (松散内存序): 只确保原子性,不具备同步性,顺序不一定,编译器,cpu都可以对ab、ef优化(改变顺序,重排)
测试代码: ( 程序中v的值有可能不是1,只是理论上的,实际没测出来)

#include <atomic>
#include <thread>
#include <assert.h>
#include <iostream>

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_relaxed);  // 2
}

void read_y_then_x()
{
    while(!y.load(std::memory_order_relaxed));  // 3
    if(x.load(std::memory_order_relaxed))  // 4
        ++z;
}

int main()
{
    for (int i=0; i < 10000; i++) {
        x=false;
        y=false;
        z=0;
        std::thread b(read_y_then_x);
        std::thread a(write_x_then_y);
        b.join();
        a.join();
        int v = z.load(std::memory_order_relaxed);
        if (v != 1)
            std::cout << v << std::endl;
    }
    return 0;
}

应用场景:自增变量,不要求是最新值

4.2 std::memory_order_release

memory_order_release(释放操作),在写入某原子对象时,

    当前线程的任何前面的读写操作都不允许重排到这个操作的后面
    去(后面的可以优化到前面去),并且当前线程的所有内存写入都在对同一个原子对象进行获
    取的其他线程可见;通常与 memory_order_acquire 或
    memory_order_consume 配对使用;
    这里意味着执行顺序 可能是
        正常 ab**c**de, 
        也可以是 后面的优化到前面去了abde**c**

测试代码如下:

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 = true x= true
    // 虽然x是松散的,但是y是释放操作,有同步性,所以它前面的x也顺带同步了,所以 read_y_then_x 中 x.load的值一定是true
}

void read_y_then_x()
{
    while(!y.load(std::memory_order_acquire));  // 3 自旋,等待y被设置为true
    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();
    std::cout << z.load(std::memory_order_relaxed) << std::endl;
    return 0;
}

应用场景:
1 多线程同步:当多个线程需要共享一个变量,并且需要确保每个线程对该变量的读操作都能够正确地获取最新值时,可以使用std::memory_order_acquire来确保同步。
2 条件变量:条件变量通常用于多个线程之间进行协作和同步。在等待条件变量时,为了避免竞态条件和死锁问题,通常会使用std::memory_order_acquire来确保等待操作能够正确执行。

4.3 std::memory_order_acquire

memory_order_acquire (获得操作): 原子性,同步性,顺序性 与memory_order_release 相反
应用场景:同4.2

4.4 std::memory_order_consume

不建议使用,已被弃用。

4.5 std::memory_order_acq_rel

获得释放操作,一个==读‐修改‐写操作==
同时具有获得语义和释放语义,即它前后的任何读写操作都不允
许重排,并且其他线程在对同一个原子对象释放之前的所有内存
写入都在当前线程可见,当前线程的所有内存写入都在对同一个
原子对象进行获取的其他线程可见;
强调:==仅保证当前线程==执行该操作前和后对目标内存位置的读写操作能够同步到主存,并不保证其他线程能够看到相同的修改顺序。

应用场景:适用于一些不需要全序列语义的多线程应用场景,它可以提供轻量级的内存序,从而避免不必要的性能开销。当程序中只需要进行acquire-release语义时可以选择使用std::memory_order_acq_rel以获得更好的性能。

类似CAS: compare_exchange_strong compare_exchange_weak
就compare_exchange_weak而言,看它的函数原型:
compare_exchange_weak(T& expected, T val,
==memory_order== success, ==memory_order== failure),参数中就成功和失败都可以指定内存序。
既涉及到读,也涉及到写。
success 读写
failure 仅写

测试代码:

#include <atomic>
#include <thread>

std::atomic<int> data(0);
bool flag = false;

void writer() {
    data.store(42, std::memory_order_release); // 写入数据
    flag = true; // 设置标志位为 true
}

void reader() {
    while (!flag); // 等待标志位为 true
    int value = data.load(std::memory_order_acquire); // 读取数据
    std::cout << "value: " << value << std::endl;
}

int main() {
    std::thread t1(writer);
    std::thread t2(reader);
    t1.join();
    t2.join();
}

4.6 std::memory_order_seq_cst

memory_order_seq_cst:顺序一致性语义。
代码简洁

x.store(true)
x.load()
还有
fetch_add
fetch_sub
fetch_add
fetch_or
fetch_xor  等都不指定内存序的时候

对于读操作相当于获得,对于写操作相当于释放,对于==读‐修改‐写操作==相当于获得释放,
是所有原子操作的默认内存序,并且会对所有使用此模型的原子操作建立一个全局顺序,
保证了多个原子变量的操作在所有线程里观察到的操作顺序相同,当然它是最慢的同步模型。

强调:==保证所有线程==执行该操作时对目标内存位置所看到的顺序是一致的,即所有线程都能够看到相同的修改顺序
文章参考与<零声教育>的C/C++linux服务期高级架构系统教程学习:链接

相关文章
|
13天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
35 6
|
4月前
|
缓存 算法 Java
Java面试题:深入探究Java内存模型与垃圾回收机制,Java中的引用类型在内存管理和垃圾回收中的作用,Java中的finalize方法及其在垃圾回收中的作用,哪种策略能够提高垃圾回收的效率
Java面试题:深入探究Java内存模型与垃圾回收机制,Java中的引用类型在内存管理和垃圾回收中的作用,Java中的finalize方法及其在垃圾回收中的作用,哪种策略能够提高垃圾回收的效率
41 1
|
4月前
|
存储 算法 Java
Java面试题:深入探究Java内存模型与垃圾回收机制,解释JVM中堆内存和栈内存的主要区别,谈谈对Java垃圾回收机制的理解,Java中的内存泄漏及其产生原因,如何检测和解决内存泄漏问题
Java面试题:深入探究Java内存模型与垃圾回收机制,解释JVM中堆内存和栈内存的主要区别,谈谈对Java垃圾回收机制的理解,Java中的内存泄漏及其产生原因,如何检测和解决内存泄漏问题
65 0
|
4月前
|
存储 算法 安全
Java面试题:Java内存模型及相关知识点深度解析,Java虚拟机的内存结构及各部分作用,详解Java的垃圾回收机制,谈谈你对Java内存溢出(OutOfMemoryError)的理解?
Java面试题:Java内存模型及相关知识点深度解析,Java虚拟机的内存结构及各部分作用,详解Java的垃圾回收机制,谈谈你对Java内存溢出(OutOfMemoryError)的理解?
72 0
|
4月前
|
设计模式 安全 Java
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
Java面试题:设计模式如单例模式、工厂模式、观察者模式等在多线程环境下线程安全问题,Java内存模型定义了线程如何与内存交互,包括原子性、可见性、有序性,并发框架提供了更高层次的并发任务处理能力
78 1
|
4月前
|
设计模式 安全 Java
Java面试题:请解释Java中的线程池以及为什么要使用线程池?请解释Java中的内存模型以及如何避免内存泄漏?请解释Java中的并发工具包以及如何实现一个简单的线程安全队列?
Java面试题:请解释Java中的线程池以及为什么要使用线程池?请解释Java中的内存模型以及如何避免内存泄漏?请解释Java中的并发工具包以及如何实现一个简单的线程安全队列?
44 1
|
4月前
|
存储 算法 Java
JAVA内存模型与JVM内存模型的区别
JAVA内存模型与JVM内存模型的区别
|
4月前
|
存储 设计模式 监控
Java面试题:如何在不牺牲性能的前提下,实现一个线程安全的单例模式?如何在生产者-消费者模式中平衡生产和消费的速度?Java内存模型规定了变量在内存中的存储和线程间的交互规则
Java面试题:如何在不牺牲性能的前提下,实现一个线程安全的单例模式?如何在生产者-消费者模式中平衡生产和消费的速度?Java内存模型规定了变量在内存中的存储和线程间的交互规则
49 0
|
4月前
|
设计模式 存储 缓存
Java面试题:结合单例模式与Java内存模型,设计一个线程安全的单例类?使用内存屏障与Java并发工具类,实现一个高效的并发缓存系统?结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
Java面试题:结合单例模式与Java内存模型,设计一个线程安全的单例类?使用内存屏障与Java并发工具类,实现一个高效的并发缓存系统?结合观察者模式与Java并发框架,设计一个可扩展的事件处理系统
39 0
|
4月前
|
设计模式 缓存 安全
Java面试题:详解单例模式与内存泄漏?内存模型与volatile关键字的实操?并发工具包与并发框架的应用实例
Java面试题:详解单例模式与内存泄漏?内存模型与volatile关键字的实操?并发工具包与并发框架的应用实例
33 0