我需要并… 不你不需要.jpg
Linux之父Linus Torvalds说过一句话:
Give it up. The whole “parallel computing is the future” is a bunch of crock.
摩尔定律逐渐失效,必须从软件设计上提高并行计算能力,而且互联网、云计算、大数据成为主流,不再是一个用户在一个机器上完成所有计算的生命周期,很多场景是不同用户在同一个机器上计算,如何快速并且互不干扰保证结果正确,是并发需要解决的问题。
多个CPU,CPU多核的出现让并行计算让摩尔定律的实效并不致命,也就意味着可以通过资源堆叠提供硬件基础,更大规模的计算可以在多核和集群的环境下实现并发,研究并发还是挺必要的。
并发是程序员的噩梦,简单的功能可能因为并发的引入而变得特别复杂,维护也比较困难,线程安全也是一个必须考虑的问题,既要实现并发又要考虑线程安全那就是一个更复杂的学问了。
Synchronous
)和异步(Asynchronous
):同步是阻塞的,必须等到返回结果后才能继续执行后续操作;异步是非阻塞的,无需等到返回结果就可以执行后续操作,异步方法会在别的线程执行,执行完成后再通知调用者。Concurrency
)和并行(Parallelism
):前者是多个任务同时提交,强调无序性;后者是同一个时间点上多个任务一起执行,强调同时性;Blocking
)和非阻塞(Non-Blocking
):阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。和同步异步的主要区别在于,同步异步更加关注获取结果的方式,是被动地获取结果;阻塞与非阻塞更关注过程,阻塞时,如果调用方法未返回结果,就把自己挂起,等到方法返回后再激活,非阻塞时,先干别的事,过一会儿再来查看有没有返回结果,是主动获取结果。Deadlock
)、饥饿(Starvation
)和活锁(Livelock
):都是多线程的活跃性问题,死锁是线程间相互占用对方需要的资源,一直在等待对方释放资源,导致所有线程无法再继续执行下去;饥饿是一个或多个线程一直得不到所需的资源,导致这些县城一直无法执行;活锁和死锁相反,两个线程相互占用对方的资源,但是两个线程都秉承“谦让”的原则,把资源主动释放给对方,就会出现资源永远不能同时被一个线程占用,线程也无法执行下去;synchronized
或重入锁),别的线程必须等到该线程释放临界区资源后才能执行;CAS(Compare and Swap)
就是一种无锁模式,在准备更改共享数据A的时候,先从主内存中拷贝一份共享数据的值A’,自己准备修改共享数据为B,那么先比较A与A’,如果相等,说明未被其他线程修改,可以执行修改,如果不等,那么重新拷贝A值到A’,再进项CAS,直到成功,其中CAS操作是原子的。RCU(Read-Copy-Update)
就是一种典型的无等待结构,对数据的读取没有任何限制,任何线程都能在第一时间读取共享数据的值,在修改数据时,先拷贝一个原始数据的副本,然后修改这个副本,再在合适的时机写回原数据,这个时机就是所有引用该数据的线程都退出对共享数据的操作。加速比: 加速比 = 优化前的系统耗时 / 优化后系统耗时
Amdahl
定律定义了串行系统并行化后加速比的计算公式和理论上限,设 T 1 T_1 T1表示只有一个CPU的情况下系统的耗时, T n T_n Tn表示使用 n n n个处理器优化后的耗时, F F F表示优化后代码中只能串行执行的比例,则:
T n = T 1 ( F + 1 n ( 1 − F ) ) T_n = T_1(F + \frac{1}{n}(1 - F)) Tn=T1(F+n1(1−F))
设系统加速比为 S S S,则
S = T 1 T n S = \frac{T_1}{T_n} S=TnT1
由上面两个等式可得:
S = T 1 T n = T 1 T 1 ( F + 1 n ( 1 − F ) ) = 1 F + 1 n ( 1 − F ) S = \frac{T_1}{T_n} = \frac{T_1}{T_1(F + \frac{1}{n}(1 - F))} = \frac{1}{F+\frac{1}{n}(1 - F)} S=TnT1=T1(F+n1(1−F))T1=F+n1(1−F)1
在CPU核数一定的情况下,系统加速比在 1 − n 1-n 1−n之间, F F F占比越低,系统加速比越高;在 F F F占比一定的情况下,系统加速比在 1 − 1 F 1 - \frac1F 1−F1之间,CPU核数越多,系统加速比越高。所以想要通过并行加速系统,必须同时考虑到增加CPU核数和串行代码并行化,否则可能并没有多大效果。
Gustafson
定律也试图解释加速比和串行占比、处理器个数之间的关系,不过切入点不同。Amdahl
定律的隐藏前提是:程序的计算量是一定的,执行完了就没有了。假设有一个实时计算系统,计算一直在进行,似乎没有停下来的迹象,没法定量分析,那怎么分析呢?这就可以用时间作为定量来分析了。
在同一段时间 t t t内,串行计算量是 a a a,可并行部分的计算量是 b b b,那么如果使用串行,t
时间内总的计算能力为 a + b a + b a+b,如果使用并行,计算能力为 a + n b a + nb a+nb,其中 n n n为处理器个数,加速比为:
S = a + n b a + b S = \frac{a + nb}{a + b} S=a+ba+nb
串行占比为:
F = a a + b F = \frac{a}{a + b} F=a+ba
有上面两个等式可得:
S = a + n b a + b = a a + b + n b a + b = F + n ( a + b − a a + b ) = F + n ( 1 − F ) = F + n − n F = n − F ( n − 1 ) S = \frac{a + nb}{a + b} = \frac{a}{a+b} + \frac{nb}{a + b} = F + n(\frac{a+b-a}{a+b}) =F +n(1-F)=F+n-nF=n-F(n-1) S=a+ba+nb=a+ba+a+bnb=F+n(a+ba+b−a)=F+n(1−F)=F+n−nF=n−F(n−1)
在 F F F或 n n n一定的情况下可以得出和Amdahl
定律一样的趋势以及上限,但是这两个定律的切入点不同:Amdahl
定律更加适合证明串行比例一定的情况下,无论增加多少CPU都是徒劳的,串行比例决定了加速比的上线;而Gustafson
更加适合证明,在串行占比很小的情况下,那么加速比就可以随着CPU个数线性增长。
由此得出结论:想让程序运行的更快,在并行占比较大的系统中,增加CPU个数是比较好的方法;在并行占比较小的系统中,增加并行占比是当务之急。