本文将介绍并发和并行有什么区别,再说明如何产生数据的竞争问题,再介绍如何解决竞争问题。

什么是并发和并行

并发和并行
并发和并行

从我画的这幅图就可以看出并发和并行的区别。

并发 是指在一个时间片内对多个任务间切换,以达到多任务的目的。并行 是指多个处理器同时运行多个任务。

简单的说就是,并发是在软件层面运行多任务,甚至单处理器都可以;而并行就需要硬件支持,需要是多处理器。

竞争的问题

由于多线程共享同一个虚拟内存空间,且多线程的顺序是不可知的,所以在多线程对数据进行操作时,就存在着很多不确定性。

拿书本中x++的例子吧,我觉得挺经典的。

假设有两个线程并发执行x++,其中x=5。以下是我们的期待输出。

时间 线程1 线程2
1 把x加载到寄存器(5)
2 寄存器值加1(6)
3 把寄存器值赋给x(6)
4 把x加载到寄存器(6)
5 寄存器值加1(7)
6 把寄存器值赋给x(7)

但是我们前面说了,线程的执行顺序是不确定的,实际情况很可能是这样。

时间 线程1 线程2
1 把x加载到寄存器(5)
2 把x加载到寄存器(5)
3 寄存器值加1(6)
4 寄存器值加1(6)
5 把寄存器值赋给x(6)
6 把寄存器值赋给x(6)

也就是说,很有可能在多线程的情况下,x++虽然被执行了多次,但结果可能只是加了1。

解决数据竞争的问题

同步

同步是解决数据竞争的一个不错的方法。

互斥

因为多线程是共享同一个内存空间的,所以无法使用普通的数据类型来判断一份数据是否已经被”操作过了”;但是我们可以使用”锁”,把数据锁住,让程序在处理数据时只允许单个线程更改数据。这就叫 互斥

再把书中的一个例子搬出来,是对ATM机存取款加锁。

1
2
3
4
5
6
7
8
9
10
11
int withdraw (struct account *account, int amount)
{
lock ();
const int balance = account->balance;
if (balance < amount) {
unlock ();
return -1;
}
account->balance = balance - amount;
unlock ();
}

如果使用Pthread库的API,还可以对单个账户加锁,在下次介绍Pthread的时候再说。

参考源