C++并发编程-14. 利用栅栏实现同步
前文我们通过原子操作实战实现了无锁队列,今天完善一下无锁的原子操作剩余的知识,包括Relaese和Acquire内存序在什么情况下是存在危险的,以及我们可以利用栅栏机制实现同步等等。
线程可见顺序
- 我们提到过除了memory_order_seq_cst顺序,其他的顺序都不能保证原子变量修改的值在其他多线程中看到的顺序是一致的。
但是可以通过同步机制保证一个线程对原子变量的修改对另一个原子变量可见。通过“Syncronizes With” 的方式达到先行的效果。
但是我们说的先行是指 “A Syncronizes With B ”, 如果A 的结果被B读取,则A 先行于B。
有时候我们线程1对A的store操作采用release内存序,而线程2对B的load采用acquire内存序,并不能保证A 一定比 B先执行。因为两个线程并行执行无法确定先后顺序,我们指的先行不过是说如果B读取了A操作的结果,则称A先行于B。
我们看下面的一段案例
#include <iostream>
#include <atomic>
#include <thread>
#include <cassert>
std::atomic<bool> x, y;
std::atomic<int> z;
void write_x()
{x.store(true, std::memory_order_release); //1
}
void write_y()
{y.store(true, std::memory_order_release); //2
}
void read_x_then_y()
{while (!x.load(std::memory_order_acquire));if (y.load(std::memory_order_acquire)) //3++z;
}
void read_y_then_x()
{while (!y.load(std::memory_order_acquire));if (x.load(std::memory_order_acquire)) //4++z;
}
// 我们写一个函数测试,函数TestAR中初始化x和y为false, 启动4个线程a,b,c,d,分别执行write_x, write_y, read_x_then_y, read_y_then_x.
void TestAR()
{x = false;y = false;z = 0;std::thread a(write_x);std::thread b(write_y);std::thread c(read_x_then_y);std::thread d(read_y_then_x);a.join();b.join();c.join();d.join();assert(z.load() != 0); //5std::cout << "z value is " << z.load() << std::endl;
}
有的读者可能会觉5处的断言不会被触发,他们认为c和d肯定会有一个线程对z执行++操作。他们的思路是这样的。
1 如果c线程执行read_x_then_y没有对z执行加加操作,那么说明c线程读取的x值为true, y值为false。
2 之后d线程读取时,如果保证执行到4处说明y为true,等d线程执行4处代码时x必然为true。
3 他们的理解是如果x先被store为true,y后被store为true,c线程看到y为false时x已经为true了,那么d线程y为true时x也早就为true了,所以z一定会执行加加操作。
上述理解是不正确的,我们提到过即便是releas和acquire顺序也不能保证多个线程看到的一个变量的值是一致的,更不能保证看到的多个变量的值是一致的。
变量x和y的载入操作3和4有可能都读取false值(与宽松次序的情况一样),因此有可能令断言触发错误。变量x和y分别由不同线程写出,所以两个释放操作都不会影响到对方线程。
看下图
即线程a执行了,但是线程d没有看见
线程b执行了,但是线程c没有看见
栅栏
有时候我们可以通过栅栏保证指令编排顺序。
看下面一段代码
#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); // 1y.store(true,std::memory_order_relaxed); // 2
}
void read_y_then_x()
{while(!y.load(std::memory_order_relaxed)); // 3if(x.load(std::memory_order_relaxed)) // 4++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
}