进程同步、信号量、临界区

进程同步与信号量、及临界区对其保护

总结:用临界区对信号量进行保护,用信号量实现进程间同步

1.进程合作?

进程合作:多进程共同完成一个任务,即合理有序的推进(通过阻塞、唤醒,“走走停停”)

实例1:
司机进程
while(true)
{
	/*等售票员关车门*/
	启动车辆
	正常行驶
	停靠车辆
	/*通知售票员车辆停靠好*/
}
售票员进程
while(true)
{
	/*等待司机停靠车辆*/
	打开车门
	售票
	关闭车门
	/*告知司机车门关闭好*/
}

合理、有序推进的关键是

找到需要同步的位置

  • 司机启动车辆之前 (司机进程)
  • 售票员打开车门之前(售票员进程)

接收到对方的消息后方可继续下一步的操作

即,发信号

2.发信号解决合作问题?
实例2:
生产者
while(true)
{
	if(count==buffer_size)
		sleep();
	...
	counter=counter+1;
	if(counter==1) wakeup(消费者);//信号
}
消费者
while(true)
{
	if(conter==0)
	sleep();
	
	...
	counter=counter-1;
	if(counter==buffer_size-1)
	wakeup(生产者);//信号
}

分析*信号* 能否解决进程间同步问题?

  1. 缓冲区满后,生产者p1想生产一个item放入时,会sleep()

  2. 又来一个生产者p2想生产一个item放入时,继续sleep()

  3. 消费者c1执行一次,消费一个item,counter-1,counter此时=buffer_size-1,故执行wakeup(生产者),唤醒p1

  4. 消费者c1又执行一次,counter-1,此时counter=buffer_size-2,p2不能被唤醒

  5. 为何出现这种现象?

根源在于:counter 的含义提供的信息过少,不足以表达有多少进程正在等待,比如counter加到buffer_size就无法继续增加,无法知道有多少生产者来过

如何解决?

——用一个变量,能承载更多信息的变量——信号量

分析如下

  1. 缓冲区满后,p1执行,sleep()value=-1 含义:一个进程等待

  2. p2执行,继续sleep() value=-2 含义:两个进程等待

  3. c1执行一次,wakeup(p1) value=-1含义:从等待队列唤醒一个进程

  4. c1又执行一次,wakeup(p2) value=0

  5. c1再执行一次 value=1含义:再来一个生产者时直接执行,无需等待

  6. p3执行 value=0 含义:用掉一个资源(此时缓冲区满),再来生产者时又需要等待

p,c根据信号量value来决定等待/唤醒

value就是一个信号量

3.靠信号还不够?从信号到信号量
信号量定义

信号量机制是由E.W.Dijkstra提出的一种同步机制,

也称P/V操作,P/V操作是信号量的一种实现。

以上面的例子来看:

  • 生产者生产前P一下信号量,决策下一步行为,生产后V一下信号量

  • 消费者消费前P一下信号量,决策下一步行为,消费后V一下信号量

  • 通过P/V操作实现信号量,用计算机语言模仿上述人脑根据value的值判断下一步的行为

实例3:
struct semaphore
{
	int value;//记录资源个数
	PCB *queue;//等待队列
}
P(semaphore s);//消费资源
V(semaphore s);//生产资源
P(semaphore s)//申请资源前P一下信号量看看
{
	s.value--;
	if(s.value<0){//表明之前要么=0没资源,要么<0还欠资源(有人还正在sleep)
	sleep(s.queue);//那么该进程也跟着sleep()
	}
}
V(semaphore s)
{
	s.value++;
	if(s.value<=0){//表明之前<0,必定有sleep()的,故从等待队列中唤醒一个
	weakup(s.queue);
	}
}

用信号量分析生产者/消费者问题

semaphore full=0;//表示消费者可用资源数(有full个东西可用消费,初值为0)
semaphore empty=buffer_size;//表示生产者可用资源数(生产者还有empty个空间可用生产,初值为buffer_size)
Producer(item)
{
	生产一个产品
	P(empty);//p(empty)的目的是生产者在执行生产之前先看一下,是否可以继续生产,是否要sleep排队等待
	P(mutex);//互斥信号量
	、、、送产品到缓冲区
	V(mutex);
	V(full);//v(full)的目的是,释放一个消费者监视的信号量full,即通知消费者可以继续消费
}
Consumer()
{
	P(full);
	P(mutex);
	、、、从缓冲区取产品
	V(mutex);
	V(empty);
	消费一个产品
}
4.有了信号量就没问题?——临界区的保护

信号量有了,就可以保证进程之间合理推进嘛?

前提是该信号量永远正确,信号量是一个共享变量,若多个进程同时去修改信号量,或者某进程修改信号量的过程中另一个进程被调度,切入了另一个进程,信号量就不能保证正确,进程间的同步也就不正确了。

如何保证信号量正确?——保证修改信号量时一次做完(原子操作)

1.某进程进入修改信号量empty的代码时,其他进程不能进入其相应的修改empty的代码——找出进程中的临界代码区

2.信号量保护的实质就是让进程中修改信号量的代码变成临界区代码

如何保护?

进入临界区之前写点代码——进入区代码

临界区

退出临界区之后写点代码——离开区代码

保护原则?
  1. 互斥进入
  2. 有空让进
  3. 有限等待
如何实现?
  1. 尝试一:轮换法(值日法)

  2. 尝试二:标记法

    以上两种不满足保护原则

  3. 轮换+标记——Peterson算法

进程P0
flag[0]=true;//进去之前打个标记
turn=1;
while(flag[1]&&turn==1);//发现P1打了标记(P1要去执行),并且轮到1(turn=1),空转,否则进入
临界区
flag[0]=false;
剩余区
c_进程P1
flag[1]=true;
turn=0;
while(flag[0]&&turn==0);//发现P0打了标记(P0要去执行),并且轮到0(turn=0),空转,否则进入
临界区
flag[1]=false;
剩余区
有没有更简单的方法?

临界区怕什么?——怕执行一半另一个进程切入

为啥另一个进程会切入?——被调度才能执行,才能切入

那进入临界区之前阻止调度可以吗?——关中断,可以

但是多核cpu时不好使

那进临界区之前加锁,出来时解锁?——好使,但是锁是啥?

锁是一个变量,进去前看一下,出来时修改一下…这个貌似与信号量概念异曲同工?

故用这个锁也需要临界区保护?

信号量保护信号量保护信号量…?——禁止套娃

将该锁做成硬件原子指令即可

临界区保护的硬件原子指令法

TS 指令

boolean TestAndSet(boolean &x)
{
	boolean temp=x;
	x=true;
	return temp;
}

lock==true 上锁空转(表示无资源)
lock==false 返回false(表示资源空闲),进入,并在TS指令中将lock修改为true(将资源上锁)

TS指令实现:
while(TestAndSet(&lock));//只有该语句通过,进入临界区时,lock一定为true
/****临界区***/
lock=false;
/****剩余区***/

Swap指令

void swap(bool &a,bool &b){
	int temp=a;
	a=b;
	b=temp;
}
为每个临界区设置bool型锁变量,如lock

swap指令实现:
bool lock=false;
bool key=true;
do{
	swap(key,lock)
}while(key);//key交换到true时空转,只有当key交换到false,即lock==false时(表示有资源空闲),进入,此时lock被交换为true
/****临界区***/
swap(key,lock);//开锁,将lock改回false
5.信号量解决实际问题的案例
5.1哲学家进餐问题
semaphore fork[5];
for(int i=0;i<5;i++)
	fork[i]=1;

progress philosopher_i(){
	while(true)
	{
        think();
        P(fork[i]);//拿起左边筷子之前P一下他,确定没人拿(回顾上述P操作含义,申请资源时需要P一下信号量,判断是否需要进入等待队列)
        P(fork[(i+1)%5]);
			eat();
		V(fork[i]);//吃完之后放下左筷子时V一下他,判断是否有人在等待这根筷子,有则唤醒他(回顾上述V操作含义,释放资源时V一下信号量,判断是否需要从等待队列中唤醒一个进程,若>0,表明无人等待,有资源可用,不用唤醒;<=0,唤醒)
		V(fork[(i+1)%5]);
	}
}

注:以上方法当五位哲学家同时拿起左边筷子时,都会卡在P(fork[(i+1)%5]),会发生死锁,解决方法之一是至多允许4位哲学家同时进餐
修改如下:
、、
semaphore tongshi=4;
、、、
while(true)
	{
        think();
        P(tongshi);//拿筷子之前P一下tongshi,若<0,则之前必定<=0,必定有4位哲学家已经开始进餐,则该进程sleep
        P(fork[i]);
        P(fork[(i+1)%5]);
			eat();
		V(fork[i]);
		V(fork[(i+1)%5]);
		V(tongshi)
	}
5.2读者-写者问题
两组并发进程:读者/写者,共享文件F
要求:
1.允许多个读者同时对文件执行读操作
2.只允许一个写者对文件执行写操作
3.任何写者在完成写操作之前不允许其他读者/写者工作
4.写者在执行写操作之前,应让已有的写者和读者全部退出
int readcount=0;
semaphore writeblock=1;//是否允许写
semaphore mutex=1;//对修改readcount计数器的互斥信号量
process reader_i(){
	P(mutex);
	readercount++;
        if(readcount ==1 )
            P(writeblock);//第一位读者阻止写者进入
	V(mutex);
		/*读文件*/
	P(mutex);
		readcount--;
        if(readcount==0)//最后一个读者允许写者进入
            V(writeblock);
	V(mutex);
}

process writer_i(){
	P(writeblock);
	/*写文件*/
	V(writeblock);
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值