1、进程同步的概念
进程异步性:各并发执行的进程以各自独立的、不可预知的速度向前推进。
操作系统因此要提供进程同步机制,来协调进程之间的相互制约关系。
2、进程互斥的概念
临界资源:一个时间段内只允许一个进程使用的资源。
临界区:进程中访问临界资源的代码段。也叫临界段。
进程互斥指当一个进程访问某临界资源时,另一个想要访问临界资源的进程必须等待。
为禁止两个进程同时进入临界区,同步机制应遵循:
空闲让进:临界区空闲,允许请求进入临界区的进程立即进入
忙则等待:当已有进程进入临界区,其他进程必须等待
有限等待:对请求访问的进程,应保证在有限时间内进入临界区
让权等待:当进程不能进入临界区时,应立即释放处理器,防止进程忙等待。
3、进程互斥的软件实现方法
单标志法
算法思想:两个进程在访问完临界区后会把使用临界区的权限转交给另一个进程。也就是说每个进程进入临界区的权限只能被另一个进程赋予。
int turn = 0; //turn表示当前允许进入临界区的进程号
P0进程: P1进程:
while(turn!=0); while(turn!=1); //进入区
critical section; critical section; //临界区
turn=1; turn=0; //退出区
remainder section; remainder section; //剩余区
运行:P0进程执行过程中,P1进程一直在检查是否轮到自己(卡在while)。P1执行完,P1才能执行。因此可以实现同一时刻最多只允许一个进程访问临界区。
单标志法存在的问题:违背空闲让进原则:
只能按P0->P1->P0…这样轮流访问,而如果当前是P0一直不访问,那么临界区空闲,但不允许P1访问。
双标志先检查
算法思想:设置一个布尔型数组flag[ ],数组中各个元素用来标记各进程想进入临界区的意愿,比如flag[0] = true意味着0号进程P0现在想要进入临界区。每个进程在进入临界区之前先检查当前有没有别的进程想进入临界区,如果没有,则把自身对应的标志flag[i]设为true,之后开始访问临界区。
bool flag[2]; //表示进入临界区意愿的数组
flag[0] = false;
flag[1] = false; //刚开始设置为两个进程都不想进入临界区
P0进程: P1进程:
while(flag[1]); while(flag[0]); //如果此时P0想进入临界区,P1就一直循环等待
flag[0] = true; flag[1] = true; //标记为P1进程想要进入临界区
critical section; critical section; //访问临界区
flag[0] = false; flag[1] = false; //访问完临界区,修改标记为P1不想使用临界区
remainder section; remainder section;
双标志先检查法存在的问题:违反忙则等待原则:
若进程并发,两个进程会同时访问临界区。
双标志后检查
算法思想:双标志先检查法的改版。前一个是先检查后上锁,这个是先上锁后检查。
bool flag[2]; //表示进入临界区意愿的数组
flag[0] = false;
flag[1] = false; //刚开始设置为两个进程都不想进入临界区
P0进程: P1进程:
flag[0] = true; flag[1] = true; //标记为P1进程想要进入临界区
while(flag[1]); while(flag[0]); //如果此时P0想进入临界区,P1就一直循环等待
critical section; critical section; //访问临界区
flag[0] = false; flag[1] = false; //访问完临界区,修改标记为P1不想使用临界区
remainder section; remainder section;
双标志先检查法存在的问题:违反空闲让进、有限等待原则:
若进程并发,两个进程会一直等待。产生饥饿现象
Peterson算法
算法思想:结合双标志法、单标志法的思想。如果双方都争着想进入临界区,那可以让进程尝试谦让。
bool flag[2]; //表示进入临界区意愿的数组,初始值都是false
flag[0] = false;
flag[1] = false; //刚开始设置为两个进程都不想进入临界区
int turn = 0;
P0进程: P1进程:
flag[0] = true; flag[1] = true; //表示自己想要进入临界区
turn = 1; turn = 0; //可以优先让对方进入临界区
while(flag[1] && turn==1); while(flag[0] && turn==0); //对方想进,且最后一次是自己谦让,那自己就循环等待
critical section; critical section; //访问临界区
flag[0] = false; flag[1] = false; //访问完临界区,表示自己不想访问临界区了
remainder section; remainder section;
1.主动争取 2.主动谦让 3.检查对方是否也想用,且最后是不是自己谦让了。
存在问题:未遵循让权等待。
4、信号量机制
通过使用操作系统提供的一对原语来对信号量操作。
信号量就是一个变量,表示系统中某种资源的数量
5、经典同步问题
1. 生产者-消费者问题
系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品并使用。
生产者、消费者共享一个初始为空、大小为n的缓冲区。
只有缓冲区没满时,生产者才能把产品放入缓冲区,否则必须等待。
只有缓冲区不空时,消费者才能从中取出产品,否则必须等待。
缓冲区是临界资源,各进程必须互斥的访问。
semaphore mutex = 1; //互斥信号量,实现对缓冲区的互斥访问
semaphore empty = n; //同步信号量,表示空闲缓冲区的数量
semaphore full = 0; //同步信号量,表示产品的数量,也即非空缓冲区的数量
producer(){ consumer(){
while(1){ while(1){
生产一个产品; P(full);
P(empty); P(mutex);
p(mutex); 从缓冲区取出一个产品;
把产品放入缓冲区; V(mutex);
V(mutex); V(empty);
V(full); 使用产品;
} }
} }
2. 读者-写者问题
允许多个读者同时对文件执行读操作。
只允许一个写者往文件中写信息。
写者写操作时不允许其他操作。
写者写操作前,应让其他读者写者退出。
int count = 0;
semaphore mutex = 1; //保护更新count变量时的互斥
semaphore rw = 1; //保证读者写者互斥的访问文件
semaphore w =1; //实现写优先
writer(){
while(1){
P(w); //在无写进程请求时进入
P(rw); //互斥访问共享文件
writing;
V(rw);
V(w);
}
}
reader(){
while(1){
P(w); //在无写进程请求时进入
P(mutex);
if(count==0)
P(rw); //阻止写进程写
count++;
V(mutex);
V(w);
reading;
P(mutex);
count--;
if(count==0)
V(rw);
V(mutex);
}
}
3. 哲学家就餐问题
semaphore chopsticks[5] = {1,1,1,1,1};
semaphore mutex = 1;
Pi(){
do{
P(mutex);
P(chopstick[i]);
P(chopstick[(i+1)%5]);
V(mutex);
eat;
V(chopstick[i]);
V(chopstick[(i+1)%5]);
think;
}while(1);
}
4. 吸烟者问题
int random;
semaphore offer1 = 0;
semaphore offer2 = 0;
semaphore offer3 = 0;
semaphore finish = 0;
process P1(){
while(1){
random=任意一个整数随机数;
random=random%3;
if(random==0)
V(offer1);
else if(random==1)
V(offer2);
else
V(offer3);
任意两种材料放在桌子上;
P(finish);
}
}
process P2(){
while(1){
P(offer3);
拿纸和胶水,卷成烟,抽掉;
V(finish);
}
}
process P3(){
while(1){
P(offer2);
拿烟草和胶水,卷成烟,抽掉;
V(finish);
}
}
process P4(){
while(1){
P(offer1);
拿烟草和纸,卷成烟,抽掉;
V(finish);
}
}