zoukankan      html  css  js  c++  java
  • C++ Memory Order

    C++11 Memory Order

    为什么需要Memory Order

    多线程下可能影响程序执行结果的行为:

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

    例子1

    int64_t i = 0;
    // Thread-1
    i = 100;
    
    // Thread2
    std::cout << i;
    

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

    例子2

    std::atomic<int> x{0};
    std::atomic<int> 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.

    例子3

    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<int> x = 0;
    std::atomic<int> 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,提供了两个线程之间的主要功能.

    1.限制指令重排

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

    2.线程之前可见

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

    std::atomic<bool> 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<bool> f1 = false;
    std::atomic<bool> 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,顺序一致会对所有的变量的所有原子操作都同步,这样,所有的原子操作就跟由一个线程执行似的.

  • 相关阅读:
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    jQuery火箭图标返回顶部代码
    fzu2020软工作业5
    [fiddler] 使用AutoResponder功能修改http响应
    [jest] 史莱姆也能学会的前端测试
    fzu2020软工作业3
    fzu2020软工作业4
    fzu2020软工作业2
    [python] 简易代码量统计脚本
  • 原文地址:https://www.cnblogs.com/gd-luojialin/p/15028134.html
Copyright © 2011-2022 走看看