一、栅栏定义
栅栏操作会对内存序列进行约束,使其无法对任何数据进行修改, 典型的做法是与使用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上不存在条件竞争, 即使它被另
外的线程修改或被其他线程读取。