进程同步与互斥

1. 进程互斥

定义:由于操作系统各进程需要使用共享资源,而这些资源需要排他性使用,各进程之间竞争使用这些资源,这些关系称为进程互斥。如:一个文件的读写问题。一个文件在被读的时候,不能同时进行写的操作。

临界资源:系统中某些资源一次只允许一个进程使用,这样的资源称为临界资源或互斥资源或共享资源

临界区:各个进程中对某个临界资源实施操作的程序片段

临界区使用原则

  1. 没有进程在临界区时,想进入临界区的进程可进入。
  2. 不允许两个进程同时在临界区。
  3. 临界区运行的进程不得阻塞其他进程进入临界区
  4. 不得使进程无限等待进入临界区

进程互斥的软件解法


  • 如果临界区之前的两个操作非原子操作的话,该解法可能导致两个进程都进入临界区,违反了临界区条件2。即如果free的初值为false,当P运行完while(free)之后,P被切换下CPU,而此时Q切换上CPU,运行完while(free)和free=true 之后进入临界区,而在某个时刻被切换下CPU,而P进CPU,那么P也将进入临界区。

进程同步与互斥_第1张图片


  • 这种利用turn来决定谁进入临界区的方法会导致一个临界区运行的进程会阻塞其他进程进入临界区。即当turn初始值为false,P进程一直在while循环等待,而假设Q如果一直都不忙于处理非临界区的操作,或者一直没进临界区,那么P进程就会一直被阻塞。

进程同步与互斥_第2张图片


  • 这种解法可能导致两个进程相互谦让而两个进程都被阻塞不能进入临界区。即当P进程运行完pturn=true语句之后,被切换下CPU,而Q此时被切换上CPU,执行完qturn=true之后,就一直在while循环等待,而这时qturn和pturn都为true,所以两个进程都不能进入临界区

进程同步与互斥_第3张图片


  • DEKKER算法:主要思想是在上面解法的基础上进入了turn变量,这个方法真正解决了互斥访问的问题,但同时引入忙等待的问题,即强制轮流的问题。

进程同步与互斥_第4张图片


  • PETERSON算法:简化了程序员编程的难度,克服了强制轮流法的缺点。考虑enter_region函数最后的while循环,如果两个进程几乎同时调用enter_region函数,则它们都将自己的进程号process保存在turn,但是只有后被保存进去的进程号才有效,前一个因被重写而失效。假如进程1是后存入的,则turn为1。当两个进程都运行到while语句时,进程0将跳出while循环进入临界区,而进程1则不断的循环直到进程0退出临界区。

进程同步与互斥_第5张图片


进程互斥的硬件解法


  • 屏蔽中断:使每个进程在刚进入临界区后立即屏蔽所有中断,在就要离开之前再打开中断。
    • 简单,高效。
    • 代价高,限制CPU并发能力。
    • 不适用于多处理器
    • 适用于操作系统本身,不适用于用户进程

  • TSL指令,即测试并加锁指令。第一条指令将lock原来的值复制到寄存器中并将lock设置为1,然后判断这个值是否为0,即寄存器中的值是否为0。如果它非零,则说明之前已被加锁,则程序回到开始并再次测试,如果是0,则进入临界区。这种方法也需忙等待。

进程同步与互斥_第6张图片


  • XCHG指令(交换指令):跟TSL指令相同,只不过复制操作变成原子性地交换两个位置的内容。

进程同步与互斥_第7张图片


2. 进程同步

定义:指系统中多个进程中发生的事件存在某种时序的关系,需要相互合作,共同完成一项任务。如如果进程A产生数据,而进程B打印数据,则B在打印之前必须等A产生数据。


生产者-消费问题

问题:
- 一个或多个生产者生产某种类型的数据放置在缓冲区中
- 有消费者从缓冲区取数据,每次取一项
- 只能有一个生产者或消费者对缓冲区进行操作。(互斥)

条件:
- 当缓冲区满时,生产者不会继续向其中添加数据
- 当缓冲区空时,消费者不会从中移走数据

进程同步与互斥_第8张图片

上面的代码可能出现竞争条件。假如现在缓冲区为空,即count=0,则如果现在消费者正在CPU上运行,其发现count的值为0,在准备sleep()的时候,消费者被切换下CPU,生产者上CPU,此时count值为0,当生产者向缓冲区加入一个数据项,count=1,则生产者会调用wakeup来唤醒消费者,而此时消费者逻辑上并未睡眠,所以wakeup信号会消失。当消费者下次运行时,它将测试先前读到的count的值(因为之前切换的时候count的值被推入了进程控制块PCB),发现为0,继续睡眠。而生产者迟早会填满整个缓冲区,然后睡眠,这样两个进程将永远睡眠下去。


信号量与PV操作

定义:一个特殊变量,用于进程传递信息的一个整数值

数据结构:

struct semaphore
{
    int count;
    queueType queue;
}

可以对信号量实施的操作:初始化,P和V。其中P,V操作都为原子操作。

P操作:

P(s)
{
    s.count--;
    if(s.count<0)
    {
        该进程状态置为阻塞状态;
        将该进程插入相应的等待队列s.queue末尾;
        重新调度;
    }
}

V操作:

V(s)
{
    s.count++;
    if(s.count<=0)
    {
        唤醒相应等待队列s.queue中等待的一个进程;
        改变其状态为就绪态,并将其插入就绪队列
    }
}

PV操作解决进程间互斥问题

主要步骤:

  • 分析并发进程间的关键活动,划定临界区
  • 设置信号量mutex,初值为1
  • 在临界区前实施P(&mutex)
  • 在临界区后实施V(&mutex)

进程同步与互斥_第9张图片


PV操作解决生产者-消费者问题

  • 同步问题 : P(&empty) ,V(&empty),P(&full) ,V(&full)

    • P(&empty): 判断是否存在空缓冲区,即是否能够向缓冲区添加数据。如果缓冲区满,则将生产者进程阻塞,并将该进程插入等待队列,重新调度。
      V(&full):更新空缓冲区数据。如果等待队列有进程,则唤醒等待队列的一个进程,也就是发消息给消费者进程缓冲区有数据了。

    • P(&full)跟V(&empty) 与上面的作用类似,不过其针对的是解决如果当缓冲区空时,消费者不会从中移走数据

-互斥问题 : P(&mutex) ,V(&mutex),将生产者添加数据和消费者移走数据分别加锁。

进程同步与互斥_第10张图片


PV操作解决读者-写者问题(互斥问题)

  • 问题描述:

    • 多个进程共享一个数据区,分为读者进程(只读数据区的数据)和写者进程(只往数据区写数据)。
  • 条件:

    • 允许多个读者同时执行读操作
    • 不允许多个写着同时操作
    • 不允许读者,写者同时操作

读者优先问题:

如果读者执行:
- 无其他读者,写者,该读者可以读
- 若已有写者等待,但有其他读者正在读,则该读者也可以读
- 若有写者正在写,该读者必须等

如果写者执行:
- 无其他读者,写者,该写者可以写
- 若有读者正在读,该写者等待
- 若有其他写者正在写,该写者等待

首先设置rc变量为读者的数量,因为允许多个读者对数据区进行访问,所以我们只需在第一个读者设置P操作,然后对最后一个读者设置V操作。同时我们应该对rc进行访问控制,防止多个读者对rc变量(共享变量)同时进行读写。

进程同步与互斥_第11张图片


你可能感兴趣的:(操作系统)