锁—信号量与管程

发布时间 2023-11-25 16:38:24作者: kiper

1.基本概念

  1. 互斥
    只有一个线程能访问临界区。

  2. 临界资源
    多个线程可以共享系统中的资源,但是临界资源只允许一个线程在某一时刻访问。如某些变量、硬件资源。

  3. 临界区
    访问临界资源的代码即临界区。

2.信号量与管程

管程(Monitors)和信号量(Semaphores)是操作系统中用于实现并发编程的两种重要技术。

2.1 信号量

2.1.1 定义

操作系统提供的一种协调共享资源访问的方法,优先级高于进程,可以保证原子性。

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

  • P(Prolaag,荷兰语尝试减少):信号量值减 1,如果信号量值小于 0,则说明资源不够用的,把进程加入等待队列。

  • V (Verhoog,荷兰语增加):信号量值加 1,如果信号量值小于等于 0,则说明等待队列里有进程,那么唤醒一个等待进程。

共享变量S只能由 PV 操作,PV的原子性由操作系统保证。
P相当获取锁,可能会阻塞线程;V相当于释放锁,不会阻塞线程。
根据同步队列中唤醒线程的先后顺序,可以分为公平和非公平两种。

信号量分类:

  • 二进制信号量:资源数目为 0 或 1。
  • 资源信号量:资源数目为任何非负值

2.1.2 例子

实现生产者消费者针对同一队列进行通信,任一时刻只有一个线程在操作队列。

假设只有一个生产者,n个消费者,那么需要三个信号量:

notFull: 表示队列不满,可以推送消息
notEmpty: 表示队列不空,可以消费消息
mutex: 表示互斥,限制同时只有一个线程访问

class Queue {

	private Semaphore notFull = new Semaphore(1);

	private Semaphore notEmpty = new Semaphore(n);

	private Semaphore mutex = new Semaphore(1);

	publib void produce() {
		notFull.P();  //队列不满才能推送,否则等待
		mutex.P();
		//生产消息加入队列
		mutex.V();
		notEmpty.V(); //唤醒等待消费线程
	}

	publib void consume() {
		notEmpty.P();  //队列不空才能消费,否则等待
		mutex.P();
		//生产消息加入队列
		mutex.V();
		notFull.V(); //唤醒等待生产者线程
	}

}

2.2 管程

管程是为了解决信号量在临界区的PV操作上的配对的麻烦而提出的并发编程方法,使用条件变量等同步机制来实现线程之间的协作。

条件变量:
线程中的一种同步机制,它允许线程等待某个条件成立,与信号量不同。
当条件不满足时,线程将自己加入等待队列,同时释放持有的互斥锁;
当一个线程唤醒一个或多个等待线程时,此时条件不一定为真(虚假唤醒)。

先后出现过三种不同的管程模型,分别是:Hasen 模型、Hoare 模型和 MESA 模型,当前广泛使用的是MESA模型。

MESA模型图示: