并发编程历史观
并发编程(Concurrency Programming),从字面意思来看,自然让人联想到两个或多个的意思。并发编程的出现是计算机硬件,操作系统进步的结果,也是计算机科学追求更快速度,更高效率解决问题的结果。
在并发出现之前
在并发出现之前,程序的执行自然就都是串行了。那时计算机CPU都是单核的,内存也非常小。需要利用计算机解决的问题也远没有现在这样复杂。因此将程序串行化自然能够解决很多问题。随着计算机软件和硬件的发展,不仅一台计算机可以有多个CPU,一个CPU还可以有多核。内存变得越来越大,速度也变得越来越快了。为了充分利用这些资源,同时为了提高计算效率和应对更大的计算量,自然而然出现了并发。
并发的基本概念
并发和并行
和并发这个概念有一字之差的就是并行。虽然只有一字之差,事实上这两个概念有很大的不同。并发是多个线程做同一件事情,并行是多个进程做不同的事情。这些进程可能一点关系也没有。举个例子,求N个数之和,我们可以将这N个数分成M分,用M个线程去分别求和,最后再由一个线程合并求和。这就是
并发。一个程序做的事情是求N个数之和,不管这个程序是一个线程完成还是多个线程完成。另一个程序做的事情是求N个数的乘积,也不管这个程序是一个线程完成还是多个线程完成。这两个线程在各自的内存区域共同受一个CPU调度,而且这两个程序还是同时进行。那这就是并行。
由并发牵扯到的一个最重要的概念就是线程(Thread),并发的手段就是多线程。因此并发编程也可以叫做多线程编程。如果没有多线程的编程绝对不能叫做并发编程。
那线程又是什么?线程可以理解为小粒度的进程,和进程的区别在于线程之间共享内存,进程之间独立内存。关于线程的形象完整表述我认为这篇文章写的非常好。
总结来说,并发是彼此之间有联系,可以共享数据。并行是彼此之间没有多少联系,做着不同的事情不会共享数据。
串行和并行
串行的反义词应该是并行。串行可以理解为一次只做一个事情,充分发挥愚公移山的精神。从微观层面来看,CPU的指令执行必定都是串行的,一个时钟周期内,单个CPU的单个核内不可能同时执行两条指令。并行的话上面说过,是多个进程做不同的事情。从微观上来看,一个多核CPU或者多个CPU在同一个时
钟周期内,执行不同的指令可以认为是并行。
单核和多核
前面说过,随着计算机硬件的发展,逐渐出现了多核甚至多个CPU(宏观上我们可以把多核和多CPU等同),这样并发编程就自然而然出现了。事实上,这种说明严格来说,是不符合历史的。我们不能说多核就一定是并发,单核就一定只能是串行。单核也可以并发,多核自然绝对可以串行。在多CPU(或者说多核
)出现之前,是有并发的。只不过这时候的并发从指令的微观层面来看,它是串行化的。一个CPU时钟周期内,一定只有一个线程在运行。因此,这个时候,多线程编程的性能和效率不一定比串行编程要高。当然,并行比串行自然效率要高。举个例子,某个程序是CPU密集计算,某个时刻正在CPU计算,另一个
程序是IO密集的,同一时刻可以调度它去进行IO计算而不影响CPU密集计算的程序,这样自然就提高了效率。
单核可以并发,也可以并行,多核的场景下进行并发编程比单核效率要高很多。这里有一个阿姆达尔定律来说明这个事情。关于阿姆达尔定律,我觉得这篇文章说的比较清楚。
并发编程的影响
很显然,并发编程带来的最大影响是提高了计算效率,充分利用了计算资源(并行也是为了充分利用计算资源)。但同时并发编程也带来了一个巨大的影响,那就是使得编写并发程序变得困难起来。编写正确的程序就已经比较难了,编写正确的并行程序就是难上加难。
编写并发程序为什么更加困难?原因在于并发程序需要处理线程调度,数据共享问题。因为数据共享带来的线程安全问题。同时,多线程就意味着需要额外的调度逻辑,这多出来的调度会带来线程的上下文切换等损耗。因此多线程不是高性能的良方。恰当地使用多线程,小心翼翼地编写并发程序才是对策。
有时候,我们甚至不能忽略线程切换带来的性能下降,更加不能容忍多线程带来的线程安全导致错误的计算结果问题。
还可以提高计算效率吗
很显然,多线程不是高性能的最好办法。可以这样说,多线程的目的在于充分利用计算机硬件资源和软件资源,使其不断处于忙碌状态从而提高生产效率。而高性能的解决办法有时候需要从更加宏观的地方入手。
分布式架构
目前,互联网里分布式架构应该比较流行。因为解决高并发,大流量问题很多时候就是分布式。现在比较流行的微服务也可以理解为更小粒度的SOA。计算机科学里还是现实生活中,我们解决大问题的思路就是拆分。将大问题拆分成小问题,将大模块拆分成小模块。然后利用多实例,多服务来解决。因>此,从宏观层面来看,分布式和并发的思想上有某种契合。都是化大为小,各个击破。
协程
多线程必然带来线程切换的问题。线程切换就会消耗额外的计算资源。因此计算机科学里又有了一种协程的说法。可以这样认为,线程是小粒度的进程,而协程可以理解为小粒度的线程。协程不需要上下文切换,在方法调用里调用其他的方法不需要切换资源。这里的关键在于利用了CPU的中断。因此避>免了线程切换带来的资源消耗。未来,Java必将支持协程。而目前Python已经支持协程了。关于协程的说明,可以参考这篇文章
协程出现后,将来的并发编程并发编程将变得更加简单还是更加复杂了呢?可以肯定的是,协程出现后,并发编程带来的效率提升又会上一个台阶。