操作系统-进程间互斥与同步

同时运行的进程要访问共享资源,此共享资源某一时刻只允许某个进程访问,其他进程需要等待,就称为进程间的互斥,操作此共享资源的代码片段称为临界去。
临界的访问要遵循四个原则:

  1. 如果没有进程在临界区,则想要进入临界区的进行可以直接进入。
  2. 如果某个进行在临界区,另外想进入临界去的进程需要等待。
  3. 正在临界区外的进程不能影响想进入临界区的进程。
  4. 进程不能无限期的等待进入临界区。

进程互斥有软件方法和硬件方法:
(1) 软件方法

  • 单标志法,即判断某个全局变量,形式为
    while{forbidden_access};
    forbidden_access = true
    进入临界区;
    离开临界区;
    forbidden_access = false
  • 双标志前检查法
  • 双标志后检查法
  • peterson法
    总结:peterson法可以实现进程互斥访问,但需要忙等待,也就是占据cpu的进程在一直检测某个条件是否达成,不能主动将进程休眠。
    (2) 硬件方法
  • 关闭中断法
    使用开、关中断指令,避免cpu的进程调度。
    缺点: 多cpu不试用、开关中断指令不会交给用户进程
  • TestAndSet指令
  • Swap指令
    后两者也是忙等待。

在以上软件、硬件的实现方法中,都存在忙等待现象,即进程不能主动休眠自己,需要不断检测某个条件是否达成,空耗cpu资源,并且会导致进程调度的“优先级反转”问题。
(优先级反转,正常情况优先级高的进程如果处于就绪态,进程调度就要执行此进程;在忙等环境中,如果优先级高的进程处于忙等,等待优先级低的进程退出临界区,而由于调度策略,cpu将不会切换到优先级低的进程去执行,则临界区将一直不会释放。)

为了可以主动让进程陷入休眠,不进行忙等,设计了两种原语:sleep和wakeup。
但在执行此原语时,一般会同判断条件放在一起,比如生产者消费者模型中,生产者进程中有:if 缓冲区已满:生产者sleep; 消费者进程中有: if 消费了一个产品后数量为buf-1: wakeup生产者。
但由于if判断和wakeup、sleep原因整体不是一个原子操作,中间可能会有cpu调度导致切换,就会产生信号丢失问题。

为了解决信号丢失,提出了一种经典的进程间同步策略:利用共享内存实现的信号量机制以及对应的p、v操作。

信号量机制

信号量机制的基本原理:两个或多个进程可以利用彼此间收发的简单信号来实现“正确的”并发执行,
一个进程在收到一个执行信号前,会被迫在一个确定的或者需要的地方停下来,从而保持同步或互斥。

  1. 数字型信号量
  2. 记录型信号量

信号量是一个整数,表示资源数,进程可以对它进行p\v操作。p操作将值减一,如果减后<0, 则说明资源耗尽,进程将自身标识存入信号量的等待队列中,将自己休眠,然后cpu进行调度,执行另外的进程;当另外的进程执行v操作–将资源数+1,如果发现结果<=0,说明有进程在等待,则唤醒等待队列中的一个进程。而由于p、v操作都是原子操作,因此判断和休眠、唤醒都是不可分割的,就不会再产生之前提到的问题。

当信号量初始值为1,就表示互斥。

使用信号亮解决生产者-消费者模型:

要满足的条件:

  1. 缓冲区不能同时访问
  2. 缓冲区满时不能写
  3. 缓冲区空时不能读
    因此同时存在同步与互斥关系,访问缓冲区时需要互斥,而生产和消费时存在同步。
Producer{
	生产一个产品;
	P(empty);
	P(mutex);
	放入缓冲区;
	V(mutex);
	V(full);
}

Customer{
	P(full);
	P(mutex);
	从缓冲区获取一个产品;
	V(empty);
	V(mutex);
	使用产品;
}

mutex表示互斥,empty表示空位的信号量、full表示产品数量的信号量。
ps: P(empty)和P(mutex)、P(full)和P(mutex)中的两个p操作不能颠倒位置。每个进程中v操作可以颠倒位置,但会扩大临界区,会降低程序性能。

使用信号亮解决读者-写者模型(读者优先)

读者写者模型,两个进程,一个只读、一个只写,不会存在一个进程又读又写,需要满足关系:
(1)当前只有读时,可以同时读
(2) 有写时,不能读不能写
(3) 有读时,不能写

因此,读进程需要面临的情况:
1 当前没有读、没有写,可以直接读
2.当前写在等待,有读正在读,则 可以直接读
3.当前正在写,则不能读
写进程需要面临的情况:
1.当前没有读、没有写,可以直接写
2.当前有读,不能写
3.当前有写,不能写

可以看到,读和写应该是互斥的,但不是每次都互斥,如果有读操作,此时新来的读可以直接读,而写则需要等到所有的读完成才可写。因此只有第一次的读会执行P操作,最后一次的读会执行V操作,而每次写操作都要执行PV操作。
使用一个全局变量cnt表示当前读进程的数量,这个数量由于多进程访问,也要加锁,因此整个逻辑如下:

读进程{
	P(mutex)
	cnt++;
	if cnt == 1 {
		P(w)
	}
	V(mutex)
	
	读操作;
	
	P(mutex)
	cnt--;
	if cnt == 0 {
		V(w)
	}
	V(mutex)
}

写进程 {
	P(w)
	写操作;
	v(w)
}
Producer{
	生产一个产品;
	P(empty);
	P(mutex);
	放入缓冲区;
	V(mutex);
	V(full);
}

Customer{
	P(full);
	P(mutex);
	从缓冲区获取一个产品;
	V(empty);
	V(mutex);
	使用产品;
}


读进程{
	P(mutex)
	cnt++;
	if cnt == 1 {
		P(w)
	}
	V(mutex)
	
	读操作;
	
	P(mutex)
	cnt--;
	if cnt == 0 {
		V(w)
	}
	V(mutex)
}

写进程 {
	P(w)
	写操作;
	v(w)
}


Produer{
	生产一个产品;
	pthread_mutex_lock();
	while(empty <=0) {
		pthread_cond_wait(mutex, cond1)
	}
	放入产品;
	pthread_cond_signal(mutex, cond2)
	pthread_mutex_unlock();
}

Customer{
	pthread_mutex_lock();  					// 加锁
	while(full <=0) {						// 判断条件,是一种同步关系
		pthread_cond_wait(mutex, cond2)		// wait是释放锁后,陷入阻塞,如果被唤醒,则重新获得锁
	}
	取出产品;
	pthread_cond_signal(mutex, cond1)		// 唤醒等待empty的生产着
	pthread_mutext_unlock();
	使用产品;

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值