目录
回顾旧知
实现进程互斥
实现进程同步
实现进程的前驱关系
本节思维导图
回顾旧知
1、一个信号量对应一种资源
2、信号量的值 = 该种资源的剩余数量(信号量值小于0,说明此时有进程在等待这种资源)
3、P(S):申请一个资源S,如果资源不够就阻塞等待
4、V(S):释放一个资源S,如果有进程在等待该资源,则唤醒一个进程
实现进程互斥
实现步骤:
- 并发进程的关键活动(单核处理机情况下各进程轮流访问访问临界区),划定临界区(如:对临界资源打印机的访问就应放在临界区)
- 设置互斥信号量mutex,初始值为1(mutex表示“进入临界区的名额”)
- 在进入区P(mutex):申请资源
- 在退出区V(mutex):释放资源
注意事项:
- 对不同的临界资源需要设置不同的互斥信号量
- P、V操作必须成对出现(缺少P操作不能保证临界资源的互斥访问,缺少V操作会导致资源永不被释放,等待进程永不被唤醒)
/*记录型信号量的定义*/
typedef struct
{
int value; //剩余资源数
struct process *L; //等待队列
}semaphore;
/*信号量机制实现互斥*/
semaphore mutex = 1; //初始化信号量
P1()
{
...
P(mutex); //使用临界资源前需要加锁
临界区代码段...
V(mutex); //使用临界区资源后需要解锁
...
}
P2()
{
...
P(mutex); //使用临界资源前需要加锁
临界区代码段...
V(mutex); //使用临界区资源后需要解锁
...
}
实现进程同步
进程同步:令各并发进程按要求有序的推进(让本来异步并发的进程互相配合,有序推进)
异步性的体现:若分配给P1进程的时间片只允许该进程执行完自己的代码1和代码2,然后就要为P2进程分配时间片,若该时间片只允许该进程执行完自己的代码4,然后就要为P1进程分配......,这就导致代码段的执行顺序是未知的
实现步骤:
- 分析什么地方需要实现“同步关系”(必须保证“一前一后”执行的两个操作或两句代码)
- 设置同步信号量S,初始值为0(信号量S表示“某种资源”)
- 在“前操作”之后执行V(S)
- 在“后操作”之前执行P(S)
(前V后P)
/*信号量机制实现同步*/
semaphore S = 0; //初始化同步信号量,初始值为0
P1()
{
代码1:
代码2;
V(S); //释放资源
代码3;
}
P2()
{
P(S);
代码4:
代码5;
代码6;
}
关于如何保证“代码4(后操作)一定是在代码2(前操作)之后执行”的解释:
- 若先执行到V(S)操作,则S++后S=1。之后执行到P(S)操作时,由于S=1,表示有可用资源,会执行S--,S的值变为0,P2进程不会执行block原语,而是继续向下执行代码4
- 若先执行到P(S)操作,由于S=0,S--后会S=-1,表示此时没有可用资源,因此P操作中会执行block原语,主动请求阻塞。之后当执行完代码2(时间片用完),继而执行V(S)操作,S++,使S变回0,由于此时有进程在该信号量对应的阻塞队列中,因此会在V操作中执行wakeup原语,唤醒P2进程。这样P2就可以继续执行代码4了
- 信号量S代表“某种资源”,刚开始是没有这种资源的,P2需要使用这种资源,而又只能由P1产生这种资源
实现进程的前驱关系
本节思维导图
~over~