当两个进程想要并发地修改一个共享变量,而没有互相保护以避免对方进程的影响时,就会发生数据竞争。
假设 A 是一个共享变量。假设 P1 和 P2 是两个访问这个变量的进程。这两个进程都在执行相同的操作:“将 A 读入临时变量(进程本地);执行 tmp = tmp + 1;将 tmp 写入 A”。如果变量 A 没有被锁保护,那么执行结果可能与预期不符。例如,以下是不锁定 A 的两种情况示例
case #1: A=0 P1: read A -> tmp1 (so tmp1 is 0) P2: read A -> tmp2 (so tmp2 is 0) P1: tmp1 = tmp1 + 1 (so tmp1 is 1) P2: tmp2 = tmp2 + 1 (so tmp2 is 1) P1: tmp1 -> write A (so A is 1) P2: tmp2 -> write A (so A is 1)
case #2: A=0 P1: read A -> tmp1 (so tmp1 is 0) P1: tmp1 = tmp1 + 1 (so tmp1 is 1) P1: tmp1 -> write A (so A is 1) P2: read A -> tmp2 (so tmp2 is 1) P2: tmp2 = tmp2 + 1 (so tmp2 is 2) P2: tmp2 -> write A (so A is 2)
为了避免这类问题,可以使用锁
A=0: P1: lock A P1: read A -> tmp1 (so tmp1 is 0) P2: lock A (so P2 is blocked) P1: tmp1 = tmp1 + 1 (so tmp1 is 1) P1: tmp1 -> write A (so A is 1) P1: unlock A (so P2 is unblocked) P2: read A -> tmp2 (so tmp2 is 1) P2: tmp2 = tmp2 + 1 (so tmp2 is 2) P2: tmp2 -> write A (so A is 2) P2: unlock A
这是一种互锁,当两个进程想要访问彼此互锁的共享变量时发生。例如,假设 A 和 B 是两个锁,P1 和 P2 是两个进程
P1: lock A P2: lock B P1: lock B (so P1 is blocked by P2) P2: lock A (so P2 is blocked by P1)进程 P1 被阻塞,因为它正在等待 P2 解锁变量 B。然而,P2 也需要变量 A 来完成其计算并释放 B。因此,我们遇到了死锁。
在这个例子中,问题非常简单。但是想象一下,在 200 万行代码(如 Linux 内核)中,有数百个锁的情况下会发生什么。 :-)