一、同步与互斥
- 临界资源:一次仅允许一个进程使用的资源(物理设备、变量、数据等)
- 临界区:进程中访问临界资源的那段代码(每个要访问临界资源的进程有属于自己的临界区)
- 进程同步:多个相互合作的进程在某些关键点上可能要互相等待后者互相交换信息!
- 互斥:由进程共享资源引起
二、互斥的实现方法
2.1 软件方法
这里列出可以完全正常工作的算法,其他算法详见教材
//flag[]表示进程是否希望进入临界区或是否正在临界区中执行
//turn用于指示允许进入临界区的进程标志
enum boolean {false,true};
boolean flag[2];
int turn;
Po:{
do{
flag[0]=true;
turn=1;
while(flag[1] && turn==1); //进程1希望或者正在临界区中,且允许进入临界区的进程号为1,进程P0等待
进程P0的临界区代码CS0;
flag[0]=false; //P0访问其临界区结束
进程P0的其他代码;
}
while(true);
}
P1:{
do{
flag[1]=true;
turn=0;
while(flag[0] && turn==0); //进程0希望或者正在临界区中,且允许进入临界区的进程号为0,进程P1等待
进程P1的临界区代码CS1;
flag[1]=false; //P1访问其临界区结束
进程P1的其他代码;
}
while(true);
}
其他算法出现各种问题的原因:临界资源的状态检查和修改两个操作没有作为一个整体来实现!
2.2 硬件方法
实践中很少单独采用软件实现互斥!!下面介绍使用硬件方法实现互斥
- 禁止中断(关中断):当一个进程正在其临界区执行代码时,禁止一切中断发生!这样其他进程就无法进行进程切换,从而保证当前进程的临界区执行完成
- 硬件指令方法:用一条指令完成标志的检查和修改两个操作,从而保证检查操作与修改操作不被打断,如TS指令、Swap指令
2.3 锁机制
在锁机制中,通过原语(原语是若干条指令构成的过程,切原语执行时不可中断)保证状态的检查和修改两个操作作为一个整体实现!
锁是一个代表资源状态的变量,0表示资源可用,1表示资源不可用
三、信号量
二中的互斥方法都存在部分缺点,从而提出了信号量机制。
3.1 信号量的定义
信号量是一个表示资源的实体,由两个成员(s,q)组成,其中s是一个具有非负初值得整型变量,q是一个初始状态为空的队列。
s的含义:s大于0,表示当前可用资源的数目;s<0,其绝对值表示因请求该资源而被阻塞等待的进程数目;s等于0,表示该时刻等待该资源的进程数目为0,但是资源已经完全使用。
struct semaphore{
int count;
queueType queue;
};
wait(semaphore s) //P操作原语
{
s.count--;
if(s.count<0)
{
阻塞该进程;
将该进程插入等待队列s.queue;
}
}
signal(semaphore s) //V操作
{
s.count++;
if(s.count<=0)
{
从等待队列s.queue取出第一个进程P;
将P插入就绪队列;
}
}
值得注意的是
- 互斥信号量的初值通常为1
- 同步信号量的初值视具体含义而定
3.2 信号量实现进程互斥
main()
{
semaphore S=1;
cobegin
P1();
P2();
coend
}
P1()
{
......
P(S);
进程P1的临界区;
V(S);
......
}
P2()
{
......
P(S);
进程P2的临界区;
V(S);
......
}
3.3 信号量实现前趋关系
以这个前趋关系为例,可以如下求解:
semaphore f1=0,f2=0,f3=0,f4=5,f5=0; //都是同步信号量,表示进程Pi是否执行完成
main()
{
cobegin
P1(); P2(); P3(); P4(); P5(); P6();
coend
}
P1()
{
.......
V(f1); V(f1); V(f1);
}
P2()
{
P(f1);
.......
V(f2);
}
P3()
{
P(f1);
.......
V(f3);
}
P4()
{
P(f1);
.......
V(f4);
}
P5()
{
P(f2);
......
V(f5);
}
P6()
{
P(f3); P(f4); P(f5);
.......
}
3.4 经典进程同步问题
- 生产者-消费者问题
- 读者写者问题
- 哲学家进餐问题
- 睡眠理发师问题
这里以生产者-消费者为例,设置两个同步信号量,一个互斥信号量
semaphore full=0; //满缓冲单元的数目
semaphore empty=n; //空缓冲单元的数目
semaphore mutex=1; //对有界缓冲区进行操作的互斥信号量
main()
{
cobegin
producer();
consumer();
coend
}
producer()
{
while(true)
{
生产一个产品;
P(empty);
P(mutex);
将一个产品送入缓冲区;
V(mutex);
V(full);
}
}
consumer()
{
while(true)
{
P(full);
P(mutex);
从缓冲区取出一个产品;
V(mutex);
V(empty);
消费一个产品;
}
}
四、管程
管程同信号量一样是一种实现同步与互斥的机制。和信号量的区别在于,管程本身实现了同步与互斥,PV操作的原语放在管程的过程中; 而使用信号量时,互斥同步属于程序员的责任,PV操作由程序员完成!
具体内容详见教材
五、进程通信
互斥与同步是低级的进程间通信,对应的PV操作是低级通信原语。高级通信则是在进程间以较高的效率传送大量数据。
5.1进程通信类型
- 共享存储器系统:即多个进程共享一片存取区域(共享内存)
- 消息传递通信系统:进程间的数据交换以消息为单位,利用系统提供的通信原语实现通信。可分为直接通信方式(消息缓冲)、间接通信方式(信箱通信)
- 管道通信系统:管道是用于连接读进程和写进程以实现他们之间通信的共享文件!!!,发送进程向共享文件发送字符流,接受进程从共享文件读字符流。
5.1 消息传递通信实例
这里以直接通信(消息缓冲通信)为例(一定要看,利于增强理解):