内存栅栏其实就是强制施加一定的内存次序,又不用更改数据,又被成为内存卡、内存屏障,其实就是相当于在代码中画出界限,规定一些操作的同步关系。
接下来我们看一个例子,如下代码,
ok,我们分析完下面的代码满足的内存栅栏的规则,我们接下面分析同步关系,根据上面的第一个分析,也就是下述代码2的释放栅栏会和能看见y存储操作的值的并且内存次序为获取的操作同步,但是我们可以看到操作4,y的载入的内存次序并不是获取啊,没关系,我们要求的只是能看见y存储操作的值都获取操作,因此我们不必拘于操作4,操作5也是能看见y存储操作的值的,因为操作4是死循环,看不见就不停止,因此我们可以得出操作2和操作5是同步的,没毛病吧。
还没完,我们继续看第二个分析,也就是下述代码操作5,会和y存储该载入值的释放操作同步,但是看完代码我们还是可以发现,y的存储操作3的内存次序也不是释放啊,没关系,我们向前看,操作3前面就是释放操作,那么获取栅栏5也就和释放栅栏2,同步了,没毛病吧,齐活,相互同步,两个内存栅栏也就同步了,因此下面代码的断言不可能触发(2同步与5,1先行与2,5先行与6,1先行与6)。这里强调一点嗷,这里是内存栅栏之间的同步,不是内存栅栏和存储、载入操作的同步嗷。为了解释这个关键点,我们看下面的例子:
如果我们把内存栅栏2,放到0的位置,那么就不能确保断言一定不会触发了,根据上面的分析,0还是会和5同步,这翁庸质疑,但是这里先行关系不再存在,也就是操作1并不一定先行与5,因为5并不一定能看到x存储的值,5只能确保看到y存储的值,因此断言是可能触发的。
#include
#include
#include
std::atomic<bool> x,y;
std::atomic<int> z;
void write_x_then_y()
{
/*栅栏放这里就不行了 在这里的话 这一线程x和y是有先行关系的
其他线程就没了*/
//std::atomic_thread_fence(std::memory_order_release);//0
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_x_then_y()
{
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_x_then_y);
a.join();
b.join();
assert(z.load() != 0);
return 0;
}
栅栏对非原子数据类型的内存次序限制,按照上面的分析我们仍然可以分析出来,1先行与2,2同步与5,5先行与6 ,因此1先行与6,断言不会触发。因此内存栅栏还可以对非原子类型的内存次序进行限制。其实不止内存栅栏可以限制非原子数据类型的内存次序,内存次序为释放和消耗的数据也可以限制非原子数据类型的内存次序,名为数据依赖,这里就不细说了,有时间单独说。
#include
#include
#include
bool x = false;
std::atomic<bool> y;
std::atomic<int> z;
void write_x_then_y()
{
x = true;//1
std::atomic_thread_fence(std::memory_order_release);//2
y.store(true,std::memory_order_relaxed);//3
}
void read_x_then_y()
{
while(!y.load(std::memory_order_relaxed));//4
std::atomic_thread_fence(std::memory_order_acquire);//5
if(x)//6
++z;
}
int main()
{
x = false;
y = false;
z = 0;
std::thread a(write_x_then_y);
std::thread b(read_x_then_y);
a.join();
b.join();
assert(z.load() != 0);
return 0;
}