什么是临界区?
每个进程中访问临界资源的那段程序称为临界区(临界资源是一次仅允许一个进程使用的共享资源)。每次只准许一个进程进入临界区,进入后不允许其他进程进入。
在多线程编程中,操作系统引入了锁机制。通过锁机制,能够保证在多核多线程环境中,在某一个时间点上,只能有一个线程进入临界区代码,从而保证临界区中操作数据的一致性。
加锁过程用如下伪码表示:
1、read lock;
2、判断lock状态;(0表示资源可用,1表示资源已被占用)
3、如果已经加锁,失败返回;
4、把锁状态设置为上锁;
5、返回成功。
原语:是由若干个机器指令构成的完成某种特定功能的一段程序,具有不可分割性,即原语的执行必须是连续的,在执行过程中不允许被中断。
上锁原语:
lock:(w锁位为1,表示已上锁)
{
while(w == 1)
{
保护现场进程CPU现场;
将现行进程的pcb插入w的等待队列;
置该进程为“等待”状态;
转进程调度;
}
w = 1; //上锁
}
开锁原语:
lock:(w锁位为1,表示已上锁)
{
while(w等待队列不为空)
{
移除等待队列首元素;
将现行进程的pcb插入w的就绪队列;
置该进程为“就绪”状态;
}
w = 0; //开锁
}
上锁转到等待状态(也就是阻塞)。
开锁转到就绪状态(也就是就绪)。
进程的pcb(Process Control Block)):系统为了管理进程设置的一个专门的数据结构,用它来记录进程的外部特征,描述进程的运动变化过程。系统利用PCB来控制和管理进程,所以PCB是系统感知进程存在的唯一标志。进程与PCB是一一对应的)
死锁的原因:
(1) 因为系统资源不足。
(2) 进程运行推进的顺序不合适,保证有先后顺序。
(3) 资源分配不当等。
死锁的必要条件.
产生死锁的四个必要条件:
(1) 互斥条件:一个资源每次只能被一个进程使用。
(2) 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不剥夺条件: 进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。存在一个进程等待序列{P1,P2,…,Pn},其中P1等待P2所占有的某一资源,P2等待P3所占有的某一 源,……,而Pn等待P1所占有的的某一资源,形成一个进程循环等待环。
解决死锁的四个方式.
1)忽略该问题。例如鸵鸟算法,该算法可以应用在极少发生死锁的的情况下。为什么叫鸵鸟算法呢,(鸵鸟策略)
2)检测死锁并且恢复。(检测与解除策略)
3)仔细地对资源进行动态分配,以避免死锁。(避免策略)
4)通过破除死锁四个必要条件之一,来防止死锁产生。(预防策略)
信号灯是一个二元数组(s,q)。可以这么理解s信号灯的状态(一个整数),s的初值不为负数(和交通灯类似)。q是它的等待队列,建立信号灯时q为空。
对信号灯状态进行改变,P调用一次-1,V调用一次+1。
P操作:
p(s)是一个原语操作,p操作执行 s– ,若s为负数,调用p(s)的进程被阻塞,放到等待队列q中。
p(s)
{
s- -;
if (s < 0)
{
保留调用进程的CPU现场;
将进程的pcb插入到s的等待队列;
把进程变为“等待状态”;
转到进程调度;
}
}
V操作
v(s)刚好与p(s)操作相反,v操作执行 s++ ,若s为大于0,继续执行;s <=0,从信号灯等待队列移出一个进程,解除等待状态,返回本程序继续执行。
v(s)
{
s++;
if (s <= 0)
{
移出s等待队列首元素;
将该进程的pcb插入就绪队列;
设置该进程为“就绪状态”;
}
}
上锁原语顺利通过可进入临界区CSa或者CSb。
main()
{
int w = 0; //互斥锁
cobegin
ppa();
ppb();
coend
}
ppa() ppb()
{ {
. .
. .
. .
lock(w); lock(w);
CSa; CSb;
unlock(w); unlock(w);
. .
. .
. .
} }
用与互斥信号灯初值mutex = 1 ;表示该资源未被占用。任何想要进入临界区的进程,必须先进行p操作(mutex为1时才能通过),访问临界资源完成后再v操作。。。
main()
{
int mutex = 0; //互斥信号灯
cobegin
Pa();
Pb();
coend
}
Pa() Pb()
{ {
. .
. .
. .
p(mutex); p(w);
CSa; CSb;
v(mutex); v(mutex);
. .
. .
. .
} }
缓冲区未空:消费者就可以一直取资源。
缓冲区未满:生产者就可以一直生产资源。
因此,同步关系保证了上面两点的实现。
方法:用两个信号灯,分别说明缓冲区数目(full和empty)
main()
{
int w = 0; //满缓冲区的数目
int empty = n; //空缓冲区的数目
int mutex = 1; //对有界缓冲区进行操作的互斥信号灯
cobegin
P1();P2()……Pm();
C1();C2()……Cn();
coend
}
producer() consumer()
{ {
while(生产未完成) while(还要继续消费)
{ {
. p(full);
. p(mutex);
. 从有界缓冲区取产品;
生产一个产品; v(mutex);
p(empty); v(empty);
p(mutex); .
送一个产品到有界缓冲区; .
v(mutex); .
v(full); 消费一个产品;
}
} }
}
生产者消费者代码参考:生产者消费者问题编程实例