1、并发程序的问题
并发是很多问题的基础,也就是操作系统设计的基础。并发包括很多设计问题,其中有进程/线程通信、资源共享与竞争(如内存、IO设备)、进程/线程同步等。由于进程/线程的相对执行速度不可预测,它取决于其他进程/线程的活动、操作系统处理中断的方式以及操作系统的调度策略,于是就产生了以下困难:
- 全局资源的共享存在危险。如果两个进程/线程对同一个全局变量进行读写操作,那么读写的执行顺序非常重要。
- 操作系统的资源分配问题。两个进程互相占用了对方进程所请求的资源,那么就会产生永久地等待,即死锁。
- 定位程序出错位置难度大。这也是由于并发程序的结果是不确定和很难复现的。
2、并发问题的源头
产生并发问题最根本的原因是CPU、内存和I/O设备之间速度的差异问题。
为了平衡这几者的差异,作出了以下努力:
- CPU增加高速缓存,平衡CPU和内存之间的速度差异;
- 操作系统增加进程/线程,进程/线程依据时间片机制并发使用CPU资源,弥补CPU等待I/O设备的时间;
- 编译器优化指令的执行次序,可以更高效地使用到缓存。
实际上,以上三个措施也是很多并发问题的来源。
1)可见性问题
可见性指的是一个线程对共享变量作出修改后,其他线程可以立刻看到。
在单核时代,所有线程都在一颗CPU上执行,同一时刻只有一个线程占用CPU。一个线程对缓存中的某一共享变量执行写操作后,另一个线程在被调度时,肯定可以读到前一个线程所写的变量值。因此,单核时代不存在可见性问题。
在多核时代,线程可能在不同的CPU上执行,每颗CPU都有自己的缓存。那么当多个线程在不同的CPU上执行时,这些线程会操作不同的CPU缓存。比如,线程t1操作CPU-1的缓存,线程t2操作CPU-2的缓存,当线程t1对某一共享变量执行写操作,然后写入CPU-1的缓存中,线程t2无法立刻读到CPU-1的缓存中这个变量的值,这时就出现了不可见问题。
下面以 java 为例,说明这个问题。
示例代码1:
public class Test{
private long count = 0;
private void add10K(){