C++ 并发编程:内存栅栏

一、栅栏定义

栅栏操作会对内存序列进行约束,使其无法对任何数据进行修改, 典型的做法是与使用memory_order_relaxed约束序的原子操作一起使用。 栅栏属于全局操作, 执行栅栏操作可以影响到在线程中的其他原子操作。 因为这类操作就像画了一条任何代码都无法跨越的线一样, 所以栅栏操作通常也被称为“内存栅栏”(memory barriers)。

二、内存栅栏使用思想

使用栅栏的一般想法是:
当一个获取操作能看到释放栅栏操作后的存储结果, 那么这个栅栏就与获取操作同步;并且, 当加载操作在获取栅栏操作前, 看到一个释放操作的结果, 那么这个释放操作同步于获取栅栏。
举一个简单的例子, 当一个加载操作在获取栅栏前, 看到一个值有存储操作写入, 且这个存储操作发生在释放栅栏后, 那么释放栅栏与获取栅栏是同步的。
虽然, 栅栏同步依赖于读取/写入的操作发生于栅栏之前/后, 但是这里有一点很重要: 同步点, 就是栅栏本身。

三、内存栅栏的应用

1. 内存栅栏可以让自由操作变的有序

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

std::atomic<bool> x,y;
std::atomic<int> z;

void write_x_then_y()
{
    x.store(true,std::memory_order_relaxed);//1
    std::atomic_thread_fence(std::memory_order_release);//2
    y.store(true,std::memory_order_relaxed);//3
}

void read_y_then_x()
{
    while(!y.load(std::memory_order_relaxed));//4
    std::atomic_thread_fence(std::memory_order_acquire);//5
    if(x.load(std::memory_order_relaxed))//6
        ++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();
    assert(z.load()!=0);//7
}

释放栅栏②与获取栅栏⑤同步, 这是因为加载y的操作④读取的是在③处存储的值。 所以, 在
①处存储x先行与⑥处加载x, 最后x读取出来必为true, 并且断言不会被触发⑦。 原先不带栅栏
的存储和加载x都是无序的, 并且断言是可能会触发的。 需要注意的是, 这两个栅栏都是必要
的: 你需要在一个线程中进行释放, 然后在另一个线程中进行获取, 这样才能构建出同步关
系。
在这个例子中, 如果存储y的操作③标记为memory_order_release, 而非memory_order_relaxed的话, 释放栅栏②也会对这个操作产生影响。 同样的, 当加载y的操作④标记memory_order_acquire时, 获取栅栏⑤也会对之产生影响.

2. 内存栅栏对非原子的操作排序

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

bool x=false;
std::atomic<bool> y;
std::atomic<int> z;

void write_x_then_y()
{
    x=true;// 1 在栅栏前存储x
    std::atomic_thread_fence(std::memory_order_release);
    y.store(true,std::memory_order_relaxed);// 2 在栅栏后存储y
}

void read_y_then_x()
{
    while(!y.load(std::memory_order_relaxed));// 3 在#2写入前, 持续等待
    std::atomic_thread_fence(std::memory_order_acquire);
    if(x)// 4 这里读取到的值, 是#1中写入
        ++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();
    assert(z.load()!=0);// 5 断言将不会触发
}

栅栏仍然为存储x①和存储y②, 还有加载y③和加载x④提供一个执行序列, 并且这里仍然有一
个先行关系, 在存储x和加载x之间, 所以断言⑤不会被触发。 ②中的存储和③中对y的加载,
都必须是原子操作
; 否则, 将会在y上产生条件竞争, 不过一旦读取线程看到存储到y的操
作, 栅栏将会对x执行有序的操作。 这个执行顺序意味着, x上不存在条件竞争, 即使它被另
外的线程修改或被其他线程读取。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值