进程间需要通信,我们需要设计一了良好的结构,不使用中断的方式实现。在本节中将讨论进程间通信(Inter Process Comminucation, IPC)的问题。
竞争条件
什么是竞争条件?
- 竞争条件 (race condition):两个或多个进程共同读写某些共享资源,而最后的执行解决取决于进行运行时间的精确时序时,这种情况称为竞争条件。
 
怎样避免竞争条件?
- 互斥 (mutual exclusion):以某种手段确保当一个进程在使用一个资源时,其他进程就不能对资源的做同样的操作;
 - 我们把共享的内存进行访问的程序片段称作临界区域 (critical region)。如果我们通过合适的安排使得两个进程不可能同时处于临界区,就能够避免竞争条件。
 
忙等待的互斥
下面列举的这些实现互斥的方案,绝对性地禁止了两个进程共享一个资源:
屏蔽中断:顾名思义,一个进程或线程进入临界区域之后立即屏蔽所有中断,离开之前再打开中断;
锁变量:一种软件层面的解决方案。即设置一个共享的锁变量,其初始值为 0:
- 一个线程想要进入临界区之前需要先测试锁变量的值,为 1 则继续等待;
 - 如果为 0,则线程进入这个临界区,并且将锁变量的值设置为 1,离开时重置;
 
显然,但其实这种方法仍然会有概率导致多个线程同时进入临界区。
严格轮换法:设置一个变量用于记录当前可以进入临界区的线程,各个线程连续测试这个变量是否出现给定的值。这种方式称为忙等待 (busy waiting),忙于等待的锁称为自旋锁 (spin lock)。
由于这种方式是浪费 CPU 时间的,通常会避免。只有在有理由认为等待时间非常短的情况下,才使用。
Peterson 解法:荷兰数学家 T.Dekker 提出的一种不需要严格轮换的软件互斥算法。后来 Peterson 发现了一种更简单的互斥算法。
这个算法的核心是
enter_region与leave_rigion这样两个函数,比如我们有 2 个线程:1 2 3 4 5 6 7 8 9 10 11 12 13 14#define N 2 // 表示一共有两个线程,下面两个函数的传参都是指线程号,共有 01 两种取值 int turn; // 当前实际可以占用资源的进程号 int interested[N]; void enter_region(int process) { int other = 1 - process; // 另一个进程号 interested[other] = TRUE; turn = process; while(turn==process && interested[other] == TRUE); } void leave_region(int process) { interested[process] = FALSE; }如果一个线程调用了
enter_region函数,但是并没有获得turn的赋值,那么说明这个线程获取资源失败,可以再次调用函数,则函数会进入等待状态。TSL指令:指 Test and Set Lock,测试并加锁。它的汇编格式如下:1TSL RX,LOCK它将一个内存字 LOCK 读取到 RX 中,并且在 LOCK 上置放一个非零值,这两步读写操作是不可分割的。
上面列举的这些方法都是正确的,但是它们都有忙等的缺点,也就是说:
- 当一个进程想要进入临界区时,先检查是否会允许进入,若不允许,该进程将原地等待直到允许为止;
 
解决这个缺点最简单的方法是 sleep 和 wakeup:
sleep:一个将引起进程阻塞的系统调用,即被挂起,知道收到被唤醒的信号;wakeup:也就是唤醒一个指定进程的操作。
消费者生产者问题
问题
考虑这样一个问题:两个进程共享一个缓冲区。其中一个生产者,将信息放入缓冲区;另一个是消费者,将信息从缓冲区中取出。一个比较正常的处理办法是以下的方式:
 |  | 
这种处理办法看起来很好,但是它是存在问题的:
- 如果消费者检测到 0,
sleep函数还没开始执行,生产者发送的wakeup信号就已经到达,那么传递给消费者的wakeup信号将丢失,两个线程有可能永久沉睡下去。 - 这个问题就叫做消费者生产者问题。
 
信号量
Dijkstra 引入了一个新的变量类型信号量 (semaphore) 用来记录线程被唤醒的次数。他建议设定两种操作,将其命名为 down 和 up,分别对应一个线程的 sleep 与 wakeup 函数,伪代码大致如下:
 |  | 
引入信号量的概念可以解决消费者生产者问题:
 |  | 
- 解决方案中一共提供了三个信号量:
- 一个称为 
full,用来记录被占用缓冲区的数目,初始值为 0; - 一个称为 
empty,用来记录空闲缓冲区的数目,初始值为 N(缓冲区的总数); - 一个称为 
mutex,锁变量,用来确保消费者生产者不会同时访问缓冲区。 
 - 一个称为 
 
信号量的另一种用途是实现同步 (synchronization)。
互斥量
如果不需要信号量的计数能力,有时可以使用信号量的简化版本互斥量 (mutex)。它仅仅适用于管理或共享一小段代码,因为互斥量实现时既容易又有效,这在实现用户线程包的时候非常有用。