线程同步就是多个线程完成同一任务而进行的协调不违反规则的合作过程。
例如线程A和线程B的合作任务就是将初值为0的变量x 加为1。程序结束后,x的值应该为1才是我们想要的。如果x没有被++仍为0,或者++了2次变为2了,都是失败的结果。
用两段伪代码模拟一下
int x=0;
A: B:
if(x==0) if(x==0)
x++; x++;
else else
Do nothing; Do nothing;
这样的运行起来的两个线程A和B,看起来没有问题,只要X为0,就++好了,如果不为0,就代表已经为1了,不用操作,所以 do nothing 。可是我们知道线程合作时cpu的掌握权是互相交换的。也许A进程刚判断完x==0是正确的,然后还没来得及++呢,就切换到了B线程中,B中判断x==0也是正确的(因为A没来得及x++),然后B线程就给x++了,x++完后变为1了,然后A线程又拿到了cpu的控制权,由于刚才已经判断过x是0了(其实现在已经被B给加成1了,但A不会返回去再判断一次),A就对x进行了一次++,现在x就变为2了,这与我们想要的是不符的!
这就是由于没有合理的处理
临界区,什么是临界区?就是A,B线程中代码重复的这部分!由于cpu来回切换,所以导致相同的代码起到的逻辑判断会有很大几率的失效。
那么如何防止两个线程同时进入临界区呢?如果在A和B的前面各加入一个判断,看对方是否进入了if else这段代码中。比如这样;
int x=0;
bool flag=true;
A:
if(flag)
{flag=false;
if(x==0)
{ x++ };
else
{ do nothing; }
}
B:
if(flag)
{flag=false;
if(x==0)
{ x++ };
else
{ do nothing; }
}
这样每次就检查了对方是否进入了if else 中,如果对方进入了,自己不进入了,好像可行。
可是再仔细看看,A,B的代码有重复了完全一样,这不就是把临界区的范围扩大了一点吗。A刚判断为flag为true后,还没来得及把flag置为false,就切换到B同样判断了flag也为true,那么这样和一开始就没什么区别!但也慢慢接近了锁的概念
那么我们就想该怎么办呢。比喻一下,好比一个房子只能进1个人,目前有2个人,不进人和进2个人都不是我们想要的,之前第一次进了2个人的原因是什么。是不是第一个人进了之后,我们没来得及判断房子里是否有人了,就让第二个人进来了。那你说那你不会判断快点?我们知道计算机能同时处理多个进程或线程也是障眼法。计算机一个时刻只能执行一条指令,只是切换速度非常的快,可比每秒24帧成动态画面的眼睛要快的千万倍。
既然有线程之间有合作就有切换,我们永远无法知道下一步到底是判断还是切换线程。如果是切换到合作的线程,再快再干着急也没用!
因此就有了锁这个概念,一个人如果能进房子,进去后就立即上锁,外面的另一个人想进也进不来。
那可能有人会问,如果一个人推开了房子确认锁是开的,准备上锁,上锁的间隙,另一个人也看房子没锁,钻进去了怎么办,那不又成了以上的问题了?
其实不必担心,其实总有一个人先进去的,不可能同时进的(起码按照计算机的原理是这样的),进去和上锁变成了同一步操作或者说是原子操作,能 上锁 就代表锁是开的,代表没人,就代表可以进。不能 上锁 就代表锁是关的,代表有人了,代表不能进。因此只有 “上锁” 这一个操作,它暗含了2步操作 1.
判断是否锁了2.
我"上锁" 或者 "等着锁开"。
线程切换不可能会把一个操作给切开。就比如上面都是说判断if(x==0)后切换到B进程,不会说是判断了个if(x== )连x和什么数比较都不读完就切换的。