信号量 & 管程

参考连接

锁原理 - 信号量 vs 管程:JDK 为什么选择管程

管程和信号量分析

并发与同步、信号量与管程、生产者消费者问题

前提知识

临界资源

虽然多个进程可以共享系统中的各种资源,但其中许多 资源一次只能为一个进程所使用,我们把一次仅允许一个进程使用的资源称为临界资源

许多 物理设备 都属于临界资源,如打印机等。此外,还有许多 变量、数据 等都可以被若干进程共享,也属于临界资源

临界区

对临界资源的访问,必须互斥地进行,在每个进程中,访问临界资源的那段代码称为临界区

互斥

只有一个线程能访问临界区

PV操作

P操作 表示申请一个资源

P操作的定义:S=S-1

  • 若S>=0,则执行P操作的线程继续执行
  • 若S<0,则置该线程为阻塞状态,并将其插入阻塞队列

V操作 表示释放一个资源

V操作定义:S=S+1

  • 若S>0则执行V操作的线程继续执行
  • 若S<0,则从阻塞状态唤醒一个线程,并将其插入就绪队列

如果 S初始值为1,那么这个semaphore就是一个mutex semaphore,效果就是临界区的 互斥访问

如果 S初始值为0,那么 做条件同步,效果就是必须等待某些条件发生

如果 S初始值为N(N一般大于1),那么 用来限制并发数目,也被称之为counting semaphone

采用信号量控制线程的 例子

信号量设置的是2,也就是同时只允许2个线程处理,当第三个线程 T3 来的时候 T1、T2 还没处理完的情况下,T3 会阻塞到 T1 或 T2 执行完成并且通过 V 操作(加1,释放一个位置给别人),这个时候 T3 进行 P操作(减一,把这个位置占用)

锁机制的实现方案有两种

信号量(Semaphere)

  • 操作系统 提供的一种 协调共享资源访问 的方法
  • 和用软件实现的同步比较,软件同步是平等线程间的的一种同步协商机制,不能保证原子性
  • 信号量则由操作系统进行管理,地位高于进程,操作系统 保证信号量的原子性

管程(Monitor)

  • 解决信号量在临界区的 PV 操作上配对的麻烦,把配对的 PV 操作集中在一起,生成的一种并发编程方法,其中使用了 条件变量 这种同步机制

在这里插入图片描述

  • 信号量将共享变量 S 封装起来,对共享变量 S 的所有操作都只能通过 PV 进行,这和面向对象的思想很像

  • 事实上,封装共享变量是并发编程的常用手段

  • 在信号量中,当 P 操作无法获取到锁时,将当前线程添加到同步队列(syncQueue)中。当其余线程 V 释放锁时,从同步队列中唤醒等待线程。但当有多个线程通过信号量 PV 配对时会异常复杂,所以管程中引入了等待队列(waitQueue)的概念,进一步封装这些复杂的操作

信号量(Semaphere)

原理

信号中包括一个整形变量,和两个原子操作 P 和 V。其原子性由操作系统保证,这个整形变量只能通过 P 操作和 V 操作改变

共享变量 S 只能由 PV 操作,PV 的原子性由操作系统保证

分类

二进制信号量:资源数目为 0 或 1

资源信号量:资源数目为任何非负值

使用场景

互斥访问

实现临界区的互斥访问注意事项:
一是信号量的初始值必须为 1;二是 PV 必须配对使用

Semaphore mutex = new Semaphore(1);
mutex.P();
// do something
mutex.V();

临界值

实现临界区的条件访问注意事项:
初始信号量必须为 0,这样所有的线程调用 P 操作时都无法获取到锁,只能进行等待队列(相当于管程中的等待队列),当其余线程 B 调用 V 操作时会唤醒等待线程

Semaphore condition = new Semaphore(0);
// ThreadA,进行等待队列中
condition.P();

// ThreadB,唤醒等待线程 ThreadA
condition.V();

阻塞队列

阻塞队列是典型的 生产者-消费者模式,任何时刻只能有一个生产者线程或消费都线程访问缓冲区。并且当缓冲区满时,生产者线程必须等待,反之消费者线程必须等待。

任何时刻只能有一个线程操作缓存区:互斥访问,使用二进制信号量 mutex,其信号初始值为 1。
缓存区空时,消费者必须等待生产者:条件同步,使用资源信号量 notEmpty,其信号初始值为 0。
缓存区满时,生产者必须等待消费者:条件同步,使用资源信号量 notFull,其信号初始值为 n

管程(Monitor)

并发编程 领域,有两大核心问题:(这两大问题,管程都是能够解决的)

互斥 ,即同一时刻只允许一个线程访问共享资源;
同步 ,即线程之间如何通信、协作

管程指管理共享变量以及对共享变量的操作过程,让他们支持并发

管程和信号量关于互斥的实现完全一样,都是 将共享变量及其操作统一封装起来,任一时刻只有一个线程在执行管程代码

条件变量

条件变量(condition variable)是管程内部的实现机制,每个条件变量都代表一种 等待的原因,也对应一个等待队列

  • 条件变量有两个操作:wait和signal,或者在加上一个signal_all操作。条件变量都是配合互斥锁一起使用,互斥锁保证了对临界资源的互斥访问
  • 简单来说,管程就是互斥锁(称之为monitor’s lock)与条件变量的配合使用

正在管程内的线程可以放弃 对管程的控制权,等待某些条件发生再继续执行

  • 不管互自旋锁还是信号量,进入了临界区域除非代码执行完,否则是不会出现线程切换的
  • 管程可以主动放弃执行权,这反映到编码上也会有一些差异

信号量 & 管程

  • 为了实现阻塞队列的功能,即等待-通知(wait-notify),除了使用互斥锁 mutex 外,还需要两个判断队满和队空的资源信号量 fullBuffers 和 emptyBuffers,使用起来不仅复杂,还容易出错
  • 管程在信号量的基础上,更进一步增加了 条件同步,将上述复杂的操作封起来

  • 信号量本质是 可共享的资源的数量
  • 管程是一种 抽象数据结构 用来限制同一时刻只有一个线程进入临界区

  • 信号量是可以 并发 的,并发量取决于S初始值
  • 管程内部同一时刻 最多只能有一个 线程执行
  • 信号量的 V操作 如果唤醒了其他线程,当前线程与被唤醒线程 并发执行
  • 对于管程的 signal操作,要么当前线程继续执行(Hansen),要么被唤醒线程继续执行(Hoare),二者不能并发

  • 信号量 与管理的资源紧耦合(即信号量S的初始值等同于资源的数目,且通过P V操作修改剩余可用的资源数量)
  • 在管程中需 自行判断 是否还有可共享的资源

  • 信号量的 P操作可能阻塞,也可能不阻塞
  • 管程的 wait操作一定会阻塞

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值