从上一篇文章《
线程同步----锁
》中我们了解了解决线程同步中最基本的一些问题,那就是如何用 锁 来保护合作线程们的临界区数据不被重复修改。但是从这里引出了一个问题,那就是如果一个线程A先进入了临界区且上了锁,等到B线程也到了临界区前发现锁是锁的,那么B只能等待直到A线程执行完临界区代码才能继续往下执行,这样就会导致cpu被浪费的占用,因为B没有做任何事情,只是等待。
因此为了提高系统的cpu利用率。有了睡觉和唤醒这两种操作,当一个线程B需要等待线程A时,将进入睡眠状态。当A做了该做的事情后,将唤醒B 。A如果需要等待B做一些操作的话,也可以进行睡觉,到时候再被B唤醒。
简单举个例子吧。假如依旧是x,我需要它始终处于一个范围,那就是0-10之间。
A线程负责给x++,只要x不到10的时候就必须给x加1 。如果x为10了,自己就去睡觉。.
B线程负责给x--,只要x不为0,B就一直让x-1。如果x为0,自己就去睡觉。
伪代码
int x=5;
A:
while(1)
{
if(x==10)
//flag2
sleep();
x++;
//flag1
if(x==1) //如果x为1则代表x++之前x是0,可能是B-1后导致的,B就可能会睡觉。
wake(B);
}
B:
while(1)
{
if(x==0)
sleep();
x--;
if(x==9) //如果x为9则代表x--之前x是10,可能是A+1后导致的,A就可能会睡觉。
wake(A);
}
但以上的睡眠和唤醒会导致几个问题。
1.使用x时都没有给它加锁,这就会导致线程切换的时候出现对x的误,
flag1的位置如果x++后为2,那是不是意味着不满足下面if(x==1)的条件,不用唤醒B,可是
flag1处发生了线程切换,B线程让x--了,x变为1了,此时再又切换回了A线程,A线程是不是就会执行它本不应该执行的wake(B),然而B现在并没有在睡眠wake(B)这局话不起任何作用。这个倒不会导致什么严重后果,出现这个因为没有给x加锁导致的,但是开头也说了,使用睡眠和唤醒这种方式就是为了改正上锁后等待的线程占用cpu的问题,再加锁岂不是本末倒置了。其实也未必,相比与给A,B线程全局加锁来说,只对一些影响结果的操作上锁等待的时间非常短,可以忽略不计。我们可以在x++前和wake后 加入锁优化这个问题。
2.出现了死锁,导致A,B线程都睡死了,没人叫了。
flag2的位置,A线程判断x已经为10了,要去睡觉了,可睡之前切换到了B, B让x--变为9,判断x==9正确,B想着该叫A起来了,可是A现在本来就清醒着。B这一wake(A)就没有用,然后切换回A,A执行了sleep就睡过去了,然后之后就是B一直让x--,直到x为0,自己也睡去了,再也没有条件可以时B去叫醒A了,然后这俩货一直睡,程序死了。
出现这个问题的原因是什么。是不是因为B发的WAKE(A)这个信号在不该执行的时候执行了,因此失去作用了。
这种问题我们可以加锁吗?显然不能的。
问题1为什么可以加锁,因为wake一下就结束了,不会持续,会立刻释放锁;但是问题2这种死锁问题,给A上了锁,A如果执行sleep要一直睡,永远释放不了锁,B线程还怎么愉快的玩耍?不照样还是死!
所以为了解决这个问题,又有了信号量这种概念。这个我们下一篇博客再说。