使用多线程几乎总是伴随着"数据的并发访问",多个线程之间毫无干系的并行运行是很罕见的。线程有可能提供数据给其他线程处理,或者是备妥必要条件(比如shared state)用以供其它线程使用,或者是供其它线程ublock.
首先我们要明确的是自C++11起:不同的对象拥有各自的内存区.
Concurrent Data Access为什么会造成问题?
1,Unsynchronized data access(未同步化的数据访问):并行运行的两个线程读写同一个内存中的数据,不知道哪个语句先来.
2,Haft-written data(写至半的数据):某个线程正在读该内存中的数据,另一个线程则尝试改动它。
3,Reordered statement(重新安排的语句):语句和操作由于编译器的优化被重新安排执行的次序,甚至不自行,也许对于一个单线程的程序是没有问题的,但是对于多线程的组合却破坏了预期的行为.
Unasynchronized Data Access:
if(val >=0 ){ //间隙1 f(val);}else{ //间隙2 f(-val);}
在单线程的环境中上述代码是没有任何问题的,但是在多线程的环境中,如果多个线程处理val的值,也就是说val的值在间隙1和间隙2处被改变!!!!!!这样不就超出我们的预期了么.
std::vector v;....if(!v.empty()){ //间隙. std::cout<< v.front() << std::endl;}
在单线程的环境中上述的代码也是没问题的,但是在多线程的环境中很有可能在间隙处v变成empty.
Half-Written Data(写至半途而废的数据):
考虑我们有一个变量:
long long x = 0;
某个线程对它写入数值:
x = -1;
另外一个线程读取它:
std::cout << x;
程序的输出是什么?也就是说当另外一个线程读取它的时候可能是:
1, 0(x的旧值)-如果第一个线程尚未赋予她-1;
2,-1(x的新值) -如果第一线程已经赋予了它-1;
3,任何其他值-如果在第一个线程对x赋予-1的过程中.
不仅仅是上面我们所举例的long long类型、即使基本数据类型如int和bool,标准也不保证读写是atomic(不可切割的,意思是独占的不可被打断的,也就是说读的时候是一定不能被写的)。
Reordered Statement(重新安排的语句):
假设有两个共享对象,一个是long类型用来将data从某个线程传递到另一个线程,另外一个是bool readyFlag,用来表示第一个线程是否已经提供了数据.
long data;bool readyFlag = false;
一般都会想到,将“某线程中对data的设定”和“另外一个线程中队data的销毁”同步化(synchronize),于是写出下面的代码.
//线程1data = 42;readyFlag = ture;
//线程2while(!readyFlag){ ;}foo(data);
在不知道任何细节的情况下,几乎一开始都会认为线程2的foo()函数肯定是在data有值42之后才会被调用的。认为对foo()的调用只有在readyFlag为ture的前提下才能触及,而为ture又发生在42赋值给data之后,因为赋值之后才令readyFlag为ture.
但是事实并非如此编译器优化过后可能重新安排语句:
readyFlag = ture;data = 42;
解决以上问题所需要的性质:
Atomicity(不可切割性):这意味着读写一个变量其行为都是独占的,排他的,无任何打断的,因此一个线程不可能读到“因另外一个线程而造成的”中间状态.
Order(次序):我们需要一些方法保证“某些具体指定语句”的次序.