用信号量同步线程
共享变量引入了同步错误 (synchronization error) 的可能性。
比如某个对等线程在将某个共享变量加载到寄存器并修改之后,在存储寄存器的值到内存之前,另一个对等线程执行了加载变量到寄存器的操作,引起同步错误。
对于一个线程,操作共享变量内容的指令构成了一个关于该共享变量的临界区 (critical section),不同线程之间的临界区不能存在交集,否则是不安全的。
我们想要确保每个线程在执行其临界区中的指令时,拥有对共享变量的互斥访问 (mutually exclusive access),这种现象称为互斥。
信号量
信号量 (semaphore) 是具有非负整数值的全局变量。
只能由两种操作进行处理 P 和 V:
$P(s)$:$\begin{equation} \left{\begin{array}{lr}if(s\ne 0),
s=s-1\if(s=0),hangthread,untils\ne0\ \end{array}\right. \end{equation}$$V(s)$:$s=s+1
$
P 操作是原子的,不可分割的,一旦预测信号量 s 变为非零,就会将 s 减一,不能有中断。
V 的加1操作也是原子的,也就是说加载、加1、存储信号量的过程不能有中断。
V 的加1操作只能重启一个阻塞在 P 的线程,有多个线程在等待同一个信号量时,不能预测 V 会重启哪一个线程。
这两个操作的定义确保了一个正确初始化了的信号量不为负,这个属性叫做信号量不变性 (semaphore invariant)。
使用信号量实现互斥
使用信号量来实现互斥的基本思想是将每个共享变量(或一组相关的共享变量)与一个信号量 s (初始为1)关联起来。
而 P 和 V 操作确保了互斥访问(即信号量不能为-1)。
在一个线程的临界区前后增加信号量操作,以这种方式保护共享变量的信号量称为二元信号量。
以提供互斥为目的的二元信号量常常称为互斥锁 (mutex)。
在一个互斥锁上执行 P 操作称为对互斥锁加锁;V 为对互斥锁解锁。
对一个互斥锁加了锁但是还没有解锁的线程称为占用这个互斥锁。
一个被用作一组可用资源的计数器的信号量被称为计数信号量。
从可操作的意义上来说,由 P 和 V 操作创建的禁止区,使得在任何时间点上,被包围的临界区中,都不可能有多个线程在执行指令。换句话说,信号量操作确保了线程对临界区的互斥访问。
利用信号量来调度共享资源
信号量的另外一个作用是调度对共享资源的访问。一个线程用一个信号量来通知另一个线程,程序状态中的某个条件已经为真。
生产者-消费者问题
生产者和消费者线程共享一个有 n 个槽的有限缓冲区。生产者线程反复地生成新的项目,并把它们插入到缓冲区中。消费者线程不断地从缓冲区中取出这些项目,然后消费(使用)它们。
读者-写者问题
是互斥问题的一个概括。一组并发的线程要访问一个共享对象。修改对象的线程叫做写者,制度对象的线程叫做读者。
写者必须拥有对对象的独占访问,而读者可以和无限多个其他读者共享对象。