详解进程同步与互斥机制

目录
  • 一、什么是进程同步
  • 二、什么是进程互斥
  • 三、常见的进程同步与互斥机制

一、什么是进程同步

在多道批处理系统中,多个进程是可以并发执行的,但由于系统的资源有限,进程的执行不是一贯到底的, 而是走走停停,以不可预知的速度向前推进,这就是进程的异步性。

那么,进程的异步性会带来什么问题呢?举个例子,如果有 A、B 两个进程分别负责读和写数据的操作,这两个线程是相互合作、相互依赖的。那么写数据应该发生在读数据之前。而实际上,由于异步性的存在,可能会发生先读后写的情况,而此时由于缓冲区还没有被写入数据,读进程 A 没有数据可读,因此读进程 A 被阻塞。

进程同步(synchronization)就是用来解决这个问题的。从上面的例子我们能看出,一个进程的执行可能影响到另一个进程的执行,所谓进程同步就是指协调这些完成某个共同任务的并发线程,在某些位置上指定线程的先后执行次序、传递信号或消息。

再举个生活中的进程同步的例子,你想要喝热水,于是你打了一壶水开始烧,在这壶水烧开之前,你只能一直等着,水烧开之后水壶自然会发生响声提醒你来喝水,于是你就可以喝水了。就是说水烧开这个事情必须发生在你喝水之前。

注意不要把进程同步和进程调度搞混了:

  • 进程调度是为了最大程度的利用 CPU 资源,选用合适的算法调度就绪队列中的进程。
  • 进程同步是为了协调一些进程以完成某个任务,比如读和写,你肯定先写后读,不能先读后写吧,这就是进程同步做的事情了,指定这些进程的先后执行次序使得某个任务能够顺利完成。

二、什么是进程互斥

同样的,也是因为进程的并发性,并发执行的线程不可避免地需要共享一些系统资源,比如内存、打印机、摄像头等。举个例子:我们去学校打印店打印论文,你按下了 WPS 的 “打印” 选项,于是打印机开始工作。 你的论文打印到一半时,另一位同学按下了 Word 的 “打印” 按钮,开始打印他自己的论文。想象一下如果两个进程可以随意的、并发的共享打印机资源,会发生什么情况?

显然,两个进程并发运行,导致打印机设备交替的收到 WPS 和 Word 两个进程发来的打印请求,结果两篇论文的内容混杂在一起了。

进程互斥(mutual exclusion)就是用来解决这个问题的。当某个进程 A 在访问打印机时,如果另一个进程 B 也想要访问打印机,它就必须等待,直到 A 进程访问结束并释放打印机资源后,B 进程才能去访问。

实际上,像上述的打印机这种在一个时间段内只允许一个进程使用的资源(这也就是互斥的意思),我们将其称为临界资源,对临界资源进行访问的那段代码称为临界区。

通俗的对比一下进程互斥和进程同步:

  • 进程同步:进程 A 应在进程 B 之前执行
  • 进程互斥:进程 A 和进程 B 不能在同一时刻执行

从上不难看出,进程互斥是一种特殊的进程同步,即逐次使用临界资源,也是对进程使用资源的先后执行次序的一种协调。

三、常见的进程同步与互斥机制

常见的进程同步与互斥机制有两种:

  • 信号量与 PV 操作
  • 管程

① 信号量与 PV 操作

包交包会!看完下面这段解释你绝对能够明白 PV 操作是啥。

1965年,荷兰学者 Dijkstra 提出了一种卓有成效的实现进程同步和互斥的方法 — 信号量机制(Semaphore)。信号量其实就是一个变量 ,我们可以用一个信号量来表示系统中某种资源的数量,比如:系统中只有一台打印机,就可以设置一个初值为 1 的信号量。

用户进程可以通过使用操作系统提供的一对原语来对信号量进行操作,从而很方便的实现进程互斥或同步。这一对原语就是 PV 操作:

1)P 操作:将信号量值减 1,表示申请占用一个资源。如果结果小于 0,表示已经没有可用资源,则执行 P 操作的进程被阻塞。如果结果大于等于 0,表示现有的资源足够你使用,则执行 P 操作的进程继续执行。

可以这么理解,当信号量的值为 2 的时候,表示有 2 个资源可以使用,当信号量的值为 -2 的时候,表示有两个进程正在等待使用这个资源。不看这句话真的无法理解 V 操作,看完顿时如梦初醒。

2)V 操作:将信号量值加 1,表示释放一个资源,即使用完资源后归还资源。若加完后信号量的值小于等于 0,表示有某些进程正在等待该资源,由于我们已经释放出一个资源了,因此需要唤醒一个等待使用该资源(就绪态)的进程,使之运行下去。

我觉得已经讲的足够通俗了,不过对于 V 操作大家可能仍然有困惑,下面再来看两个关于 V 操作的问答:

问:信号量的值 大于 0 表示有临界资源可供使用,这个时候为什么不需要唤醒进程?

答:所谓唤醒进程是从就绪队列(阻塞队列)中唤醒进程,而信号量的值大于 0 表示有临界资源可供使用,也就是说这个时候没有进程被阻塞在这个资源上,所以不需要唤醒,正常运行即可。

问:信号量的值 等于 0 的时候表示没有临界资源可供使用,为什么还要唤醒进程?

答:V 操作是先执行信号量值加 1 的,也就是说,把信号量的值加 1 后才变成了 0,在此之前,信号量的值是 -1,即有一个进程正在等待这个临界资源,我们需要唤醒它。

信号量和 PV 操作具体的定义如下:

实现进程互斥

两步走即可实现进程的互斥:

  • 定义一个互斥信号量,并初始化为 1
  • 把对于临界资源的访问置于 P 操作和 V 操作之间

P 操作和 V 操作必须成对出现。缺少 P 操作就不能保证对临界资源的互斥访问,缺少 V 操作就会导致临界资源永远得不到释放、处于等待态的进程永远得不到唤醒。

实现进程同步

回顾一下进程同步,就是要各并发进程按要求有序地运行。

举个例子,以下两个进程 P1、P2 并发执行,由于存在异步性,因此二者交替推进的次序是不确定的。假设 P2 的 “代码4” 要基于 P1 的 “代码1” 和 “代码2” 的运行结果才能执行,那么我们就必须保证 “代码4” 一定是在 “代码2” 之后才会执行。

如果 P2 的 “代码4” 要基于 P1 的 “代码1” 和 “代码2” 的运行结果才能执行,那么我们就必须保证 “代码4” 一定是在 “代码2” 之后才会执行。

使用信号量和 PV 操作实现进程的同步也非常方便,三步走:

  • 定义一个同步信号量,并初始化为当前可用资源的数量
  • 在优先级较高的操作的后面执行 V 操作,释放资源
  • 在优先级较低的操作的前面执行 P 操作,申请占用资源

配合下面这张图直观理解下:

生产者和消费者问题

下面我们利用信号量和 PV 操作来解决经典的进程同步和互斥问题:生产者和消费者问题。

【问题描述】:系统中有一组生产者进程和一组消费者进程,生产者进程每次生产一个产品放入缓冲区,消费者进程每次从缓冲区中取出一个产品并使用。任何时刻,只能有一个生产者或消费者可以访问缓冲区。

由题可知,生产者、消费者共享一个初始为空、大小为 n 的缓冲区,我们从题目中提炼出同步与互斥关系:

  • 同步关系 1:只有缓冲区没满时(优先级高),生产者才能把产品放入缓冲区(优先级低),否则必须等待
  • 同步关系 2:只有缓冲区不空时(优先级高),消费者才能从中取出产品(优先级低),否则必须等待
  • 互斥关系:缓冲区是临界资源,各进程必须互斥地访问。

既然这个题目有两个同步关系和一个互斥关系,那么我们就需要两个同步信号量和一个互斥信号量:

  • empty:同步信号量(对应同步关系 1),表示生产者还能生产多少,即还能放入缓冲区多少产品,该数量小于等于 0,则生产者不能进行生产。 初始化为 n。
  • full:同步信号量(对应同步关系 2),表示消费者还能从缓冲区取出多少,即当前缓冲区已有产品的数量,该数量小于等于 0,则消费者不能进行读取。初始化为 0。
  • mutex:互斥信号量,实现对缓冲区的互斥访问。初始化为 1。

代码如下,注意各个 PV 操作的配对:

② 管程

管程有一个重要特性:在一个时刻只能有一个进程使用管程。进程在无法继续执行的时候不能一直占用管程,否则其它进程将永远不能使用管程。也就是说管程天生支持进程互斥。

其实使用管程是能够实现信号量的,并且也能用信号量实现管程。但是管程封装的比较好,相比起信号量来需要我们编写的代码更少,更加易用,这也就是 Java 采用管程机制的原因,synchronized 关键字及 wait()notify()notifyAll() 这三个方法都是管程的组成部分。把管程翻译为 Java 领域的语言,就是管理类的成员变量和成员方法,让这个类是线程安全的。

以上就是详解进程同步与互斥机制的详细内容,更多关于进程同步与互斥机制的资料请关注我们其它相关文章!

(0)

相关推荐

  • PHP进程同步代码实例

    经常遇到这样一种情况,计划任务定时后台执行某个php程序,有时候也需要手动执行,可能多个人都需要执行这个程序,如果任务持续时间非常长,就很容易造成重复执行,所以就开发了下面的类. 作用:在实际代码运行前检查与当前相同操作的进程是否正在运行,高并发运行是可靠的,运行中的进程中途异常中断不会产生任何影响. 构造方法传递pid文件目录的绝对路径,需要自己保证不同进程对应不同pid文件. 复制代码 代码如下: <?php /*  * 同一个PHP进程只运行一次,根据进程名字判断是否为排重进程,只能运行于

  • C++线程间的互斥和通信场景分析

    互斥锁(mutex) 为了更好地理解,互斥锁,我们可以首先来看这么一个应用场景:模拟车站卖票. 模拟车站卖票 场景说明: Yang车站售卖从亚特兰蒂斯到古巴比伦的时光飞船票:因为机会难得,所以票数有限,一经发售,谢绝补票. 飞船票总数:100张: 售卖窗口:3个. 对于珍贵的飞船票来说,这个资源是互斥的,比如第100张票,只能卖给一个人,不可能同时卖给两个人.3个窗口都有权限去售卖飞船票(唯一合法途径). 不加锁的结果 根据场景说明,我们可以很快地分析如下: 可以使用三个线程来模拟三个独立的窗口

  • 详解C语言进程同步机制

    本文是对进程同步机制的一个大总结(9000+字吐血总结),涵盖面非常的全,包括了进程同步的一些概念.软件同步机制.硬件同步机制.信号量机制和管程机制,对每种机制结合代码做了详细的介绍,并且对琐碎的知识点和概念解释的非常清晰. ​ 在前面的博客中讲述了进程的状态及其状态的转换,每种状态的含义和转换的原因.同样我们也知道,在OS引入了进程后,可以使系统中的多道程序可以并发的执行,进程的并发执行一方面极大的提高了系统的资源利用率和吞吐量,但是另一方面却使系统变得更加复杂,如果不能采取有效的措施,对多个

  • C#多线程学习之(六)互斥对象用法实例

    本文实例讲述了C#多线程学习之互斥对象用法.分享给大家供大家参考.具体分析如下: 如何控制好多个线程相互之间的联系,不产生冲突和重复,这需要用到互斥对象,即:System.Threading 命名空间中的 Mutex 类. 我们可以把Mutex看作一个出租车,乘客看作线程.乘客首先等车,然后上车,最后下车.当一个乘客在 车上时,其他乘客就只有等他下车以后才可以上车.而线程与Mutex对象的关系也正是如此,线程使用Mutex.WaitOne()方法等待Mutex对 象被释放,如果它等待的Mutex

  • 浅谈Java多线程实现及同步互斥通讯

    Java多线程深入理解本文主要从三个方面了解和掌握多线程: 1. 多线程的实现方式,通过继承Thread类和通过实现Runnable接口的方式以及异同点. 2. 多线程的同步与互斥中synchronized的使用方法. 3. 多线程的通讯中的notify(),notifyAll(),及wait(),的使用方法,以及简单的生成者和消费者的代码实现. 下面来具体的讲解Java中的多线程: 一:多线程的实现方式 通过继承Threa类来实现多线程主要分为以下三步: 第一步:继承 Thread,实现Thr

  • Python实现进程同步和通信的方法

    Python中的多线程其实并不是真正的多线程,如果想要充分地使用多核CPU的资源,在python中大部分情况需要使用多进程.Python提供了非常好用的多进程包multiprocessing,只需要定义一个函数,Python会完成其他所有事情.借助这个包,可以轻松完成从单进程到并发执行的转换.multiprocessing支持子进程.通信和共享数据.执行不同形式的同步,提供了Process.Queue.Pipe.Lock等组件. 引例: 如之前创建多进程的例子 # -*- coding:utf-

  • Python多进程同步简单实现代码

    本文讲述了Python多进程同步简单实现代码.分享给大家供大家参考,具体如下: #encoding=utf8 from multiprocessing import Process, Lock def func(lock, a): lock.acquire() print a lock.release() if __name__ == '__main__': lock = Lock() workers = [] # 创建两个进程 for i in range(0, 2): p = Process

  • 详解进程同步与互斥机制

    目录 一.什么是进程同步 二.什么是进程互斥 三.常见的进程同步与互斥机制 一.什么是进程同步 在多道批处理系统中,多个进程是可以并发执行的,但由于系统的资源有限,进程的执行不是一贯到底的, 而是走走停停,以不可预知的速度向前推进,这就是进程的异步性. 那么,进程的异步性会带来什么问题呢?举个例子,如果有 A.B 两个进程分别负责读和写数据的操作,这两个线程是相互合作.相互依赖的.那么写数据应该发生在读数据之前.而实际上,由于异步性的存在,可能会发生先读后写的情况,而此时由于缓冲区还没有被写入数

  • 详解Spring的核心机制依赖注入

    详解Spring的核心机制依赖注入 对于一般的Java项目,他们都或多或少有一种依赖型的关系,也就是由一些互相协作的对象构成的.Spring把这种互相协作的关系称为依赖关系.如A组件调用B组件的方法,可称A组件依赖于B组件,依赖注入让Spring的Bean以配置文件组织在一起,而不是以硬编码的方式耦合在一起 一.理解依赖注入 依赖注入(Dependency Injection) = 控制反转(Inversion ofControl,IoC):当某个Java实例(调用者)需另一个Java实例(被调

  • 详解struts2的token机制和cookie来防止表单重复提交

    详解struts2的token机制和cookie来防止表单重复提交 今天在做一个投票系统时要实现防止表单重复提交! 当时就想到了用struts2提供的token机制 struts2的token机制防止表单重复提交: 首先需要在提交的jsp页面(要使用token机制,必须使用struts2提供的标签库)加上 <s:token></s:token> 这段代码,然后在struts.xml里面需要进行如下配置: <action name="token" class

  • 详解java中反射机制(含数组参数)

    详解java中反射机制(含数组参数) java的反射是我一直非常喜欢的地方,因为有了这个,可以让程序的灵活性大大的增加,同时通用性也提高了很多.反射原理什么的,我就不想做过大介绍了,网上一搜,就一大把.(下面我是只附录介绍下) Reflection 是Java被视为动态(或准动态)语言的一个关键性质.这个机制允许程序在运行时透过Reflection APIs取得任何一个已知名称的class的内部信息,包括其modifiers(诸如public, static 等等).superclass(例如O

  • 详解java实践SPI机制及浅析源码

    1.概念 正式步入今天的核心内容之前,溪源先给大家介绍一下关于SPI机制的相关概念,最后会提供实践源代码. SPI即Service Provider Interface,属于JDK内置的一种动态的服务提供发现机制,可以理解为运行时动态加载接口的实现类.更甚至,大家可以将SPI机制与设计模式中的策略模式建立联系. SPI机制: 从上图中理解SPI机制:标准化接口+策略模式+配置文件: SPI机制核心思想:系统设计的各个抽象,往往有很多不同的实现方案,在面向的对象的设计里,一般推荐模块之间基于接口编

  • 详解Python中import机制

    Python语言中import的使用很简单,直接使用import module_name语句导入即可.这里我主要写一下"import"的本质. Python官方定义: Python code in one module gains access to the code in another module by the process of importing it. 1.定义: 模块(module):用来从逻辑(实现一个功能)上组织Python代码(变量.函数.类),本质就是*.py文

  • 详解JavaScript 的执行机制

    一.关于javascript javascript是一门单线程语言,在最新的HTML5中提出了Web Worker,但javascript是单线程这一核心仍未改变. 为什么js是单线程的语言?因为最初的js是用来在浏览器验证表单操纵DOM元素的.如果js是多线程的话,两个线程同时对一个DOM进行了相互冲突的操作,那么浏览器的解析是无法执行的. Web Worker 的作用,就是为 JavaScript 创造多线程环境,允许主线程创建 Worker 线程,将一些任务分配给后者运行.在主线程运行的同

  • 详解 MySQL的FreeList机制

    一.前言 MySQL启动后,BufferPool就会被初始化,在你没有执行任何查询操作之前,BufferPool中的缓存页都是一块块空的内存,未被使用过也没有任何数据保存在里面. 而且你也知道了通过缓冲页的描述信息可以直接且唯一的找到它所指向的缓存页. 那你有没有想过,我们从磁盘里面读取出来的 数据页 应该放到那个缓冲页中去呢? 这个问题就引出了Free List. 二.Free List 其实Free List是Buffer Pool中基于缓存页描述信息 组织起来的双向链表.换言之,Free

  • 详解Java 虚拟机垃圾收集机制

    1 垃圾收集发生的区域 之前我们介绍过 Java 内存运行时区域的各个部分,其中程序计数器.虚拟机栈.本地方法栈三个区域随线程共存亡.栈中的每一个栈帧分配多少内存基本上在类结构确定下来时就已知,因此这几个区域的内存分配和回收都具有确定性,不需要考虑如何回收的问题,当方法结束或线程结束,内存自然也跟着回收了 而 Java 堆和方法区这两个区域则有显著的不确定性,只有在程序运行时我们才能知道程序究竟创建了哪些对象,创建了多少对象,所以这部分内存的分配和回收是动态的,垃圾收集器所关注的正是这部分内存该

  • 详解Vue 的异常处理机制

    最近需要在业务中加一个全局的 filter,filter 会对输入进行验证,用于进行前端监控.其中一个要处理的问题,就是验证失败后如何发送异常日志,这个过程中顺便了解了一下 vue 的异常处理机制. errorCaptured.errorHandler vue 提供了两个 API 用于异常的捕获,分别是 errorCaptured和 errorHandler: errorCaptured errorCaptured 是组件的一个钩子函数,用于在组件级别捕获异常.当这个钩子函数返回 false 时

随机推荐