基本概念
进程的互斥:由于竞争使用同一共享资源而产生相互制约的关系(间接制约关系)
进程的同步:进程之间通过在执行时序上的某种限制而达到相互合作的约束关系(直接制约关系)
临界资源:以互斥方式使用的共享资源,一次只允许一个进程使用(eg:打印机、磁带机)
进入区:申请进入临界区的那段代码
临界区:进程中访问临界资源的那段代码
退出区:退出对临界区访问的那段代码
剩余区:除进入区、临界区、退出区之外的代码
互斥准则:
- 空闲让进
- 忙则等待
- 有限等待
- 让权等待(当一个进程不能进入临界区时要立即阻塞自己,释放处理机让其他进程使用,避免“忙等”)
硬件同步机制
测试与设置指令(Test_and_Set)
交换指令(Exchange)
信号量机制
信号量机制由一个称为信号量的特殊变量和两个被命名为P操作( wait() )和V操作( signal() )的原语组成。
信号量:若一个资源被视为临界资源时,为它定义一个独立的信号量
P操作:当一个进程需要互斥使用某个临界资源时,执行对信号量的该操作,了解资源的空闲情况
V操作:当使用完该临界资源后,执行信号量的该操作,让其他需要使用该资源的进程感知到该临界资源已经可以使用
整形信号量——未遵循让权等待的原则
记录型信号量
//信号量定义
typedef struct{
int count; //信号量的值,>0:资源的数量;<0:|S.count|表示等待此资源的进程数目
queueType *queue; //信号量等待队列指针
}semaphore;
// wait(S)操作
semaphore S;
void wait(S)
{
S.count--;
if(S.count<0){
block(S.queue); //阻塞该进程
链接到S.queue队列;
}
}
//signal(S)操作
void signal(S)
{
S.count++;
if(S.count<=0) wakeup(S.queue); //从S.queue队列中移除一个进程,把该进程链接到就绪队列
}
信号量用于互斥
//任一进程Pi的框架
void Pi()
{
semaphore mutex=1;
do{
wait(mutex);
critical section; //临界区
signal(mutex);
remainder section; //剩余区
}while(true);
}
信号量用于同步
例:有两个并发进程P1和P2,共享一个公共信号量S,初值为0。P1执行的程序中有一条S1语句,P2执行的程序中有一条S2语句。而且,只有当P1执行完S1语句后,P2才能开始执行S2语句。
semaphore S=0; //信号量初值为0
//进程P1程序框架
void P1()
{
...
S1;
signal(S);
...
}
//进程P2程序框架
void P2()
{
...
wait(S);
S2;
...
}
void main()
{
Parbegin(P1(),P2()); //Parbegin:挂起主程序,初始化并发进程P1和P2
}
经典进程同步问题
生产者-消费者问题
问题描述:有若干生产者进程在生产产品,假设生产者进程为k(k>0)个,并将这些产品提供给m( m>0)个消费者进程去消费。
问题求解:为使生产者进程与消费者进程能并发执行,在两者之间设置了一个具有n个缓冲区的缓冲池(假设组织成环形),生产者进程将所生产的产品放入缓冲区中;消费者进程可从一个缓冲区中取出产品。假定它们的约束条件有以下4个:
- 当缓冲池中有空缓冲区时,允许任一生产者进程把产品放入。
- 当缓冲池中无空缓冲区时,则试图将产品放入缓冲区的任何生产者进程必须等待。
- 当缓冲池中有产品时,允许任一个消费者进程把其中的一个产品取出消费。
- 当缓冲池中没有产品时,试图从缓冲池内取出产品的任何消费者进程必须等待。
问题分析:对所有生产者和消费者进程来说,可以把缓冲池视为一个整体,缓冲池是临界资源,即任何一个进程在对缓冲池中某个缓冲区进行“放入”放“取出”操作时必须和其他进程互斥执行。下面用信号量机制解决这个问题,首先定义信号量如下:
- 信号量mutex,初值为1,用于控制互斥地访问缓冲池。
- 信号量full,初值为0,用于资源计数。full 值表示当前缓冲池中“满”缓冲区的个数。
- 信号量empty,初值为n,用于资源计数。empty 值表示当前缓冲池中“空”缓冲区的个数。
伪代码呈现
const int n; //buffer size
int in, out=0; //缓冲区首空、首满指针
semaphore empty=n;
semaphore full=0;
semaphore mutex=1;
void producer()
{
while(true){
生产一个产品;
wait(empty);
wait(mutex);
将产品放入缓冲区;
in = (in+1)%n;
signal(mutex);
signal(full);
}
}
void consumer()
{
while(true){
wait(full);
wait(mutex);
从缓冲区中取产品;
out = (out+1)%n;
signal(mutex);
signal(empty);
消费该产品;
}
}
void main()
{
parbegin( producer(), consumer() );
}
注:wait()和signal()不能互换
读者-写者问题
问题描述:有一个数据区(数据区可以是一个文件、一块内存空间或一组寄存器)被多个用户共享,其中一部分用户是读者,另一部分是写者。我们规定:读者对数据区是只读的,而且允许多个读者同时读;写者对数据区是只写的,当一个写者正在向数据区写信息的时候,不允许其他用户使用。即保证一个写者进程必须与其他进程互斥地访问共享对象。
问题求解:必须满足的条件如下:
- 任意多个读者可以同时读
- 任一时刻只能有一个写者可以写
- 如果写者正在写,那么读者就不能读
将数据区访问的程序视为临界区。但是,数据区不是绝对的临界资源,因为它允许多个读者用户同时使用。因此,该问题有两种解决方案。
读者优先:读者优先是指一旦有一个写者访问数据区,只要还有一个读者在进行读操作,后续的读者就不需要等待,可以保持对数据区的控制,而写者必须等待所有的读者读完才可以进行写操作。
问题分析:
- 使用一个互斥信号量wmutex,实现读者与写者、写者与写者之间的互斥。由于读者可以同时访问数据区,因此我们让第一 一个准备进入临界区的读者,通过wait(wmutex)强占临界资源,以便排斥写者访问。当最后一个读者离开临界区时,通过signal(wmutex)将临界资源释放,以便其他用户使用。
- 设计一个专为读者共享的变量readcounter,用来记录读数据区的人数。readcounter应视为临界资源,故应有一个互斥信号量,定义为rmutex。
伪代码呈现
int readcounter;
semaphore wmutex=1;
semaphore rmutex=1; //对readcounter的互斥访问
void reader()
{
while(true){
wait(rmutex);
readcounter++;
if(readcounter==1) wait(wmutex);
signal(rmutex);
readunit();
wait(rmutex);
readcounter--;
if(readcounter==0) signal(wmutex);
signal(rmutex);
}
}
void writer()
{
while(true){
wait(wmutex);
readunit();
signal(wmutex);
}
}
void main()
{
readcounter = 0;
par begin( reader(), writer() );
}
写者优先:只要有写者申请写操作,就不允许新的读者访问数据区。
问题分析:在前面定义的基础上,写者优先的解决方法增加了以下的信号量和变量:
- 信号量rsem用于在有写者访问数据区时,屏教所有的读者
- 变量writecount控制rsem的设置
- 信号量y控制writecount的修改
除此之外。对于读进程,还必须添加一个额外的信号量z。因为在rsem上不允许多于一个进程在排队, 否则写进程将不能跳过这个队列。因此只允许一个读进程在rsem上排队,而所有其他读进程在等待rsem之前,在信号量z上排队。下表 总结了写者优先方法中进程队列的各种情况。
队列状态 | 操作 |
---|---|
系统中只有读者 | 设置信号量wsem;没有排队 |
系统中只有写者 | 设置信号量wsem和rsem;写者在信号量wsem上排队 |
存在读者和写者且读者优先 | 读者设置信号量wsem;写者设置信号量rsem;所有的写者在wsem上排队;只有一个读者在rsem上排队,其他读者在信号量z上排队 |
存在读者和写者且写者优先 | 读者设置信号量wsem;写者设置信号量rsem;写者在wsem上排队;只有一个读者在rsem上排队,其他读者在信号量y上排队 |
伪代码呈现
int readcount, writecount;
semaphore x=1, y=1, z=1, wsem=1, rsem=1;
void reader()
{
while(true){
wait(z);
wait(rsem);
wait(x);
readcount++;
if(readcount==1) wait(wsem);
signal(x);
signal(rsem);
signal(z);
readunit();
wait(x);
readcount--;
if(readcount==0) signal(wsem);
signal(x);
}
}
void writer()
{
while(true){
wait(y);
writecount++;
if(writecount==1) wait(rsem);
signal(y);
wait(wsem);
writeunit();
signal(wsem);
wait(y);
writecount--;
if(writecount==0) signal(rsem);
signal(y);
}
}
void main()
{
readcount = writecount = 0;
parbegin( reader(), writer() );
}
哲学家就餐问题(略)
参考:《计算机操作系统》电子工业出版社