并发编程 竞态条件

什么是竞态条件?竞态条件是指多个进程(线程、协程)读写某些共享数据,而最后的结果取决于进程运行的准确时序。也就是当多个进程(线程、协程)竞争同一资源时,如果对资源的访问顺序敏感,就称存在竞态条件。

在一般的操作系统中,不同的进程可能会分享一块公共的存储区域,例如内存或者是硬盘上的文件,这些进程都允许在这些区域上进行读写。

操作系统有一些职责,来协调这些使用公共区域的进程之间以正确的方式进行想要的操作。这些进程之间需要通信,需要互相沟通,有商有量,才能保证一个进程的动作不会影响到另外一个进程正常的动作,进而导致进程运行后得不到期望的结果。在操作系统概念中,通常用 IPC(Inter Process Communication,即进程间通信)这个名词来代表多个进程之间的通信。

为了解释什么是竞态条件(race condition),我们引入一个简单的例子来说明:

一个文件中保存了一个数字 n,进程 A 和进程 B 都想要去读取这个文件的数字,并把这个数字加 1 后,保存回文件。假设 n 的初始值是 0,在我们理想的情况下,进程 A 和进程 B 运行后,文件中 n 的值应该为 2,但实际上可能会发生 n 的值为 1。我们可以考量一下,每个进程做这件事时,需要经过什么步骤:

  • 读取文件里 n 的值;
  • 令 n = n + 1;
  • 把新的 n 值保存回文件。

在进一步解释竞态条件之前,必须先回顾操作系统概念中的几个知识点:

  • 进程是可以并发运行的,即使只有一个 CPU;
  • 操作系统的时钟中断会引起进程运行的重新调度;
  • 除了时钟中断,来自其它设备的中断也会引起进程运行的重新调度。

假设进程 A 在运行完步骤 1 和 2,但还没开始运行步骤 3 时,发生了一个时钟中断,这个时候操作系统通过调度,让进程 B 开始运行,进程 B 运行步骤 1 时,发现 n 的值为 0,于是它运行步骤 2 和 3,最终会把 n = 1 保存到文件中。之后进程 A 继续运行时,由于它并不知道在它运行步骤 3 之前,进程 B 已经修改了文件里的值,所以进程 A 也会把 n = 1 写回到文件中。这就是问题所在,进程 A 在运行的过程中,会有别的进程去操作它所操作的数据。

所以,唯一能让 n = 2 的方法,只能期望进程 A 和进程 B 按顺序分别完整地运行完所有步骤。

我们可以给竞态条件下一个定义:

两个或者多个进程读写某些共享数据,而最后的结果取决于进程运行的准确时序,称为竞态条件。 在上述的文字中,我们使用进程作为对象来讨论竞态条件,实际上对于线程也同样适用,这里的线程包含但不限于内核线程、用户线程。因为在操作系统中,进程其实是依靠线程来运行程序的。

下一章:并发编程 临界区和互斥

1. 临界区什么是临界区?导致竞态条件发生的代码区称作临界区。在临界区中使用适当的同步就可以避免竞态条件。我们再来看这个例子:一个文件中保存了一个数字 n,进程 A 和进程 B 都想要去读取这个文件的数字,并把这个数 ...