进程同步
进程同步的定义
进程同步:我们把异步环境下的一组并发进程因直接制约而互相发送消息,互相合作、互相等待,使得各进程按照一定的速度执行的过程。
- 并发:指两个或多个事件在统一时间间隔内发生(强调时间片)
- 并行:指两个或多个事件在同一时刻发生(强调时间点)
进程两种形式的制约关系
1.互斥关系:多程序并发执行时,由于共享系统资源(如I/O设备、CPU等),这些并发的程序会形成制约关系。(有第三方资源介入)
2.同步关系:为了完成一个任务,会建立两个或者多个进程,这些进程会为了完成任务而相互合作。
临界资源
进程在使用它们时都需要采用互斥方式,这样的资源被称为临界资源
临界区、进入区、退出区
区域 | 含义 |
---|---|
临界区 | 人们把在每个进程中访问临界资源的那段代码成为临界区 |
进入区 | 临界区前面的用于检查是否可以进去的代码 |
退出区 | 恢复临界区正在使用的标志 |
解决临界区问题的同步机制应该遵循以下四个原则
- 空闲让进:临界资源处于空闲状态,允许一个请求进入
- 忙则等待:临界资源正在被访问时,其他试图进入临界去的进程必须等待,以保证对临界资源的互斥访问
- 有限等待:保证进程在有限时间进入自己的临界区,避免**“死等”**
- 让权等待:(非必要)进程不能进入临界区时,应立即释放处理机,以免进程陷入**“死等”**
实现同步机制的几种方式
-
软件同步机制:程序员利用代码实现上述四个原则(很难做到尽善尽美)
-
硬件同步机制:使用CPU硬件来实现同步,灵活度低,实现了原子操作,(详细请看原子操作)
- 关中断
- 利用test-and-set指令实现互斥(未完成让权等待原则)
- 利用swap指令实现进程互斥,对换指令,三条语句可融合成一条
-
信号量同步机制:
wait操作和signal操作都是CPU自带的原子操作
pv操作又称wait,signal原语。
主要是操作进程中对进程控制的信号量的加减控制。wait(p)用法:
wait(num),num是信号量,wait的作用是使其(信息量)减一。
如果信息量>=0,则该进程继续执行;否则该进程置为等待状态,排入等待队列。
signal(v)用法:
signal(num),num是信号量,signal的作用是使其(信息量)加一。
如果信息量>0,则该进程继续执行;否则释放队列中第一个等待信号量的进程。-
整型信号量,
wait(S) /*wait { while(S<=0); S--; } signal(S) { S++; }
未完成让权等待
-
记录型信号量
typedef struct { int value; struct process_control_block *list; } wait { S->value--; if(S->value<0) block(S->list); //此处体现了让权等待 } signal(semaphore *s) { S->value++; if(S->value<=0) //block队列中还有进程在等待资源 wakeup(S->list); }
s->value是某种资源的数目,其<0时说明该进程会变成阻塞态
|value|=block中的进程数
首次完成同步机制的四大问题
-
And型信号量
记录型信号量会产生如图所示死锁现象,And型信号量的基本思想则是:将进程在运行过程中所需的所有资源,一次性全部分配给进程,待进程使用完之后再一起释放
-
解决死锁问题:And将两个原子语句变成一个原子语句
如果有一个资源>0,一个资源<0,则该进程不能执行,会进入(缺少的资源的)block队列,并将减去的资源值恢复
信号量集
> Swait(s1,t1,d1;sn,dn,tn); T为门限制,Si>=ti,否则不予分配
>
> swait(s,d,d),此时信号集当中只有一个信号量s,但允许它每次访问d个资源,当现有资源数少于d时,不予分配
>
> swait(s,1,1) 退化成为一个记录型信号量(s>1)或互斥信号量(s=1)
>
> swait(s,0,1)可控开关,s>=1时允许进入某个特定的临界区,s=0时,阻止进入特定区
经典的进程同步问题
1.生产者消费者问题
int in=0,out=0;
item buffer[n];
semaphore mutex=1,empty=n,full=0;
void producer()
do{
producer an item nexp;
...
wait(empty); //缓冲区是否有空位
wait(mutex); //确定是否可以使用资源 两者不能互换位置,可能会导致死锁
buffer[in]=nextp;
in=(in+1)%n;
signal(mutex);
}while(TURE);
void consumer()
do{
wait(full);
wait(mutex); //Swait(empty,mutex)此处使用AND信号量更好
nextc=buffer[out];
out=(out+1)%n;
signal(mutex);
signal(empty);
consumer the item in nextc;
...
}while(TRUE);
2.哲学家进餐问题
semaphore chopstick[5]={1,1,1,1,1}
do{
wait(chopstick[i]);
wait(chopstick[(i+1)%5]);
...
//eat
...
signal(chopstick[i]);
signal(chopstick[(i+1)%5]);
...
//think
...
}while(TRUE);
三种方式:鸽笼原理、and信号量、奇数拿左边偶数拿右边
3.读者写者问题
读者优先
semaphore rmutex=1,wmutex=1; //rmutex是读者与读者之间的互斥信号量、wmutex是读者写者互斥信号量
int readcount=0;
void reader()
do{
wait(rmutex);
if(readcount==0) wait(wmutex); //锁住不让写者进入
readcount++;
signal(rmutex);//释放读读互斥信号量,让读者可以一直进入
...
perform read opearation;
...
wait(rmutex);
readcount--; //readcount的改变都要经过rmutex信号量
if(readcount==0) signal(wmutex);
signal(rmutex);
}while(TRUE);
void write()
do{
wait(wmutex);
perform write ope;
signal(wmutex);
}while(TRUE);
写者优先
semaphore r,w=1;rcm=1,wcm=1;// rcm是读读互斥,wcm是读写互斥
int rc=0,wc=0; //rc是readcount,wc是writecount
void writer()
do{
p(wcm)
if(wc=0) p(r);
wc++;
v(wcm);
p(w);
...
v(w);
p(wcm);
wc--;
if(wc==0) v(r);
v(wcm);
}while(TRUE);
void reader()
do{
p(r);v(r); //此处体现写者优先
p(rcm);
if(rc==0) p(w);
v(rcm);
p(r);
..写
v(r);
p(rcm);
rc--;
if(rc==0) v(w);
v(rcm);
}while(TRUE);