苏呆呆的博客 苏呆呆的博客
首页
  • C++
  • Java
  • Go
  • 面向对象
  • 设计模式
  • 领域驱动设计
  • Spring
常用工具
  • 本站

    • 分类
    • 标签
    • 归档
  • 我的

    • 关于
    • 收藏
GitHub (opens new window)

苏呆呆

一个向往美好生活的笨人。
首页
  • C++
  • Java
  • Go
  • 面向对象
  • 设计模式
  • 领域驱动设计
  • Spring
常用工具
  • 本站

    • 分类
    • 标签
    • 归档
  • 我的

    • 关于
    • 收藏
GitHub (opens new window)
  • Qt原子操作

    • 背景
      • 问题
        • 解决方案
          • QAtomicInt函数说明
            • T QAtomicInteger::load() const
            • T QAtomicInteger::loadAcquire() const
            • bool QAtomicInteger::ref()
            • void QAtomicInteger::store(T newValue)
            • void QAtomicInteger::storeRelease(T newValue)
            • bool QAtomicInteger::testAndSetAcquire(T expectedValue, T newValue)
            • bool QAtomicInteger::testAndSetOrdered(T expectedValue, T newValue)
            • bool QAtomicInteger::testAndSetRelaxed(T expectedValue, T newValue)
            • bool QAtomicInteger::testAndSetRelease(T expectedValue, T newValue)
        su-dd
        2023-03-15
        随笔
        目录

        Qt原子操作

        # 背景

        很久很久很久以前,CPU忠厚老实,一条一条指令的执行我们给它的程序,规规矩矩的进行计算和内存的存取。

        很久很久以前, CPU学会了Out-Of-Order,CPU有了Cache,但一切都工作的很好,就像很久很久很久以前一样,而且工作效率得到了很大的提高。

        很久以前,我们需要多个CPU一起工作,于是出现了传说中的SMP系统,每个CPU都有独立的Cache,都会乱序执行,会打乱内存存取顺序,于是事情变得复杂了……

        # 问题

        由于每个CPU都有自己的Cache,内存读写不再一定需要真的作内存访问,而是直接从Cache里面操作,同时CPU可能会在合适的时候对于内存访问进行重新排序以提高效率,在只有一个CPU的时候,这很完美。

        而当有多个CPU的时候:从Cache到内存的flush操作通常是被延迟的,所以就需要某种方法保证CPU A进行的内存写操作真的可以被CPU B读取到。

        CPU可能会因为某些原因(比如某两个变量同在一个Cacheline中)而打乱

        1. 实际内存写入顺序
        2. 实际内存读取顺序

        所以就需要某种方法保证在需要的时候

        1. 之前的读写操作已经完成
        2. 未来的读写操作还没开始

        考虑一个例子: ​​Thread A:

        while (flag == 0)
                ; // do nothing
        printf("%d\n", data);
        
        1
        2
        3

        ​​​​Thread B:

        data = 523;
        flag = 1;
        
        1
        2

        ​​这里data代表了某种数据,它可以像这里一样是一个简单的整数,也可能是某种复杂的数据结构

        总之:我们在Thread B中对 data 进行了写入,并利用 flag 变量表示 data 已经准备好了。 在Thread A中,一个忙等待直到发现 data 已经准备好了,然后开始使用 data ,这里是简单的把 data 打印出来。 现在考虑如果CPU发现对于 data 和 flag 的写入,如果按照先写入 flag 后写入 data 的方式进行,或者考虑由于Cache的 flush 操作的延迟,使得内存中变量的实际修改顺序是先 flag 后 data ,那么都将导致Thread A的结果不正确。事实上,由于内存读入操作同样是可能乱序进行的,Thread A甚至可能在读入 flag 进行判断之前就已经完成了对 data 的读入操作,这同样导致错误的结果。

        # 解决方案

        在这个例子中,我们的需求是,Thread A中对于 flag 判断时,后面的任何读入操作都没有开始,Thread B中对于 flag 写入时,任何之前的写入操作都已经完成。

        在Linux内核中,smp_rmb()、smp_wmb()、smp_mb()就是用来解决这类问题

        mb表示memory barrier rmb表示读操作不可跨越(注意,不是人民币的意思:-P),也就是我们这个例子中的Thread A所需要的 wmb表示写操作不可跨越,也就是这里Thread B所需要的 mb集合了rmb和wmb的能力,读写操作都不可跨越

        在Qt中,其支持原子操作的类QAtomicInt支持四种类型的操作, Relaxed 、Acquired 、 Release 、 Ordered

        其中:

        Relaxed 最为简单,就是不做特殊要求,由编译器和处理器对读写进行合适的排序

        Acquired 表示原子操作之后的内存操作不可被重排至原子操作之前

        Release 表示原子操作之前的内存操作不可被重排至原子操作之后

        Ordered 表示 Acquired + Release

        在前面的例子中:

        Thread A对于 flag 的读取操作需要 Acquired Thread B对于 flag 的写入操作需要 Release

        在实际实现中,不同体系结构的实现方法各不相同,很多RISC机器提供了专门的指令用于实现mb,而在x86上面,通常使用lock指令前缀加上一个空操作来实现,注意当然不能真的是nop指令,但是可以用来实现空操作的指令其实是很多的,比如Linux中采用的addl $0, 0(%esp)。

        Qt的不同类型原子操作由于本身就需要进行某种可被lock前缀修饰的操作,所以就不需要画蛇添足的再写一条空操作了,比如 testAndSetOrdered 就可以直接使用lock cmpxchgl 实现。

        # QAtomicInt函数说明

        # T QAtomicInteger::load() const

        Atomically loads the value of this QAtomicInteger using relaxed memory ordering. The value is not modified in any way, but note that there's no guarantee that it remains so.

        # T QAtomicInteger::loadAcquire() const

        Atomically loads the value of this QAtomicInteger using the "Acquire" memory ordering. The value is not modified in any way, but note that there's no guarantee that it remains so.

        # bool QAtomicInteger::ref()

        Atomically increments the value of this QAtomicInteger. Returns true if the new value is non-zero, false otherwise. This function uses ordered memory ordering semantics, which ensures that memory access before and after the atomic operation (in program order) may not be re-ordered.

        # void QAtomicInteger::store(T newValue)

        Atomically stores the newValue value into this atomic type, using relaxed memory ordering.

        # void QAtomicInteger::storeRelease(T newValue)

        Atomically stores the newValue value into this atomic type, using the "Release" memory ordering.

        # bool QAtomicInteger::testAndSetAcquire(T expectedValue, T newValue)

        Atomic test-and-set. If the current value of this QAtomicInteger is the expectedValue, the test-and-set functions assign the newValue to this QAtomicInteger and return true. If the values are not the same, this function does nothing and returns false. This function uses acquire memory ordering semantics, which ensures that memory access following the atomic operation (in program order) may not be re-ordered before the atomic operation.

        # bool QAtomicInteger::testAndSetOrdered(T expectedValue, T newValue)

        Atomic test-and-set. If the current value of this QAtomicInteger is the expectedValue, the test-and-set functions assign the newValue to this QAtomicInteger and return true. If the values are not the same, this function does nothing and returns false. This function uses ordered memory ordering semantics, which ensures that memory access before and after the atomic operation (in program order) may not be re-ordered.

        # bool QAtomicInteger::testAndSetRelaxed(T expectedValue, T newValue)

        Atomic test-and-set. If the current value of this QAtomicInteger is the expectedValue, the test-and-set functions assign the newValue to this QAtomicInteger and return true. If the values are not the same, this function does nothing and returns false. This function uses relaxed memory ordering semantics, leaving the compiler and processor to freely

        # bool QAtomicInteger::testAndSetRelease(T expectedValue, T newValue)

        Atomic test-and-set. If the current value of this QAtomicInteger is the expectedValue, the test-and-set functions assign the newValue to this QAtomicInteger and return true. If the values are not the same, this function does nothing and returns false. This function uses release memory ordering semantics, which ensures that memory access before the atomic operation (in program order) may not be re-ordered after the atomic operation.

        编辑 (opens new window)
        #QAtomicInt
        上次更新: 2023/03/15, 11:29:34
        最近更新
        01
        并发
        03-16
        02
        方法
        03-13
        03
        HelloWorld
        03-13
        更多文章>
        Theme by Vdoing | Copyright © 2022-2023 daidai | 皖ICP备2023000523号-1
        • 跟随系统
        • 浅色模式
        • 深色模式
        • 阅读模式