理解并发和并行的区别
任务描述
如图:
任务是将左边的一堆柴全部搬到右边烧掉,每个任务包括三个过程:取柴,运柴,放柴烧火。
这三个过程分别对应一个函数:
func get { geting } func carry { carrying } func unload { unloading }
串行模式
串行表示所有任务都一一按先后顺序进行。串行意味着必须先装完一车柴才能运送这车柴,只有运送到了,才能卸下这车柴,并且只有完成了这整个三个步骤,才能进行下一个步骤。
和稍后所解释的并行相对比,串行是一次只能取得一个任务,并执行这个任务。
假设这堆柴需要运送4次才能运完,那么当写下的代码类似于下面这种时,那么就是串行非并发的模式:
for(i=0;i<4;i++){ get() carry() unload() }
或者,将三个过程的代码全部集中到一个函数中也是如此:
func task { geting carrying unloading } for(i=0;i<4;i++){ task() }
这两种都是串行的代码模式。画图描述:
并行模式
并行意味着可以同时取得多个任务,并同时去执行所取得的这些任务。并行模式相当于将长长的一条队列,划分成了多条短队列,所以并行缩短了任务队列的长度。
并行的效率从代码层次上强依赖于多进程/多线程代码,从硬件角度上则依赖于多核cpu。
对于单进程/单线程,由于只有一个进程/线程在执行,所以尽管同时执行所取得的多个任务,但实际上这个进程/线程是不断的在多任务之间切换,一会执行一下这个,一会执行一下那个,就像是一个人在不同地方之间来回奔波。所以,单进程/线程的并行,效率比串行更低。
对于多进程/多线程,各进程/线程都可以执行各自所取得的任务,这是真正的并行。
但是,还需要考虑硬件层次上cpu核心数,如果只有单核cpu,那么在硬件角度上这单核cpu一次也只能执行一个任务,上面多进程/多线程的并行也并非真正意义上的并行。只有多核cpu,并且多进程/多线程并行,才是真正意义上的并行。
如下图,是多进程/多线程(2个工作者)的并行:
并发
并发是一种现象,表示多个任务同时涌入的情况。
比如同时500个http请求涌向了web服务器。实际上对于cpu来说,操作系统上要执行的那些进程也是一种并发现象:这些等待执行的进程全部都需要cpu。
区分并发和并行是很简单的,并发是一种现象,并行是一种执行模式,是处理并发现象的一种方案。
有时候也将并发当成任务,比如500并发数意味着500个任务,这500个任务可以是单进程/单线程方式处理的,这时表示的是并发不并行的模式(coroutine就是典型的并发不并行),也可以是多进程/多线程方式处理的,这时表示的是并发且并行模式。
要解决大并发问题,通常是将大任务分解成多个小任务。由于可能会从任一小任务处执行:
-
可能出现一个小任务执行了多次,还没开始下个任务的情况。这时一般会采用队列或类似的数据结构来存放各个小任务的成果
-
可能出现还没准备好第一步就执行第二步的可能。这时,一般采用多路复用或异步的方式,比如只有准备好产生了事件通知才执行某个任务
- 可以多进程/多线程的方式并行执行这些小任务,也可以单进程/单线程执行这些小任务,这时很可能要配合多路复用才能达到较高的效率
看图非常容易理解:
上图中将一个任务中的三个步骤取柴、运柴、卸柴划分成了独立的小任务,有取柴的老鼠,有运柴的老鼠,有卸柴烧火的老鼠。
如果上图中所有的老鼠都是同一只,那么是串行并发的,如果是不同的多只老鼠,那么是并行并发的。
总结
并行和串行:
- 串行:一次只能取得一个任务并执行这一个任务
- 并行:可以同时通过多进程/多线程的方式取得多个任务,并以多进程或多线程的方式同时执行这些任务
- 注意点:
- 如果是单进程/单线程的并行,那么效率比串行更差
- 如果只有单核cpu,多进程并行并没有提高效率
- 从任务队列上看,由于同时从队列中取得多个任务并执行,相当于将一个长任务队列变成了短队列
- 如果是单进程/单线程的并行,那么效率比串行更差
并发:
- 并发是一种现象:一次性涌入多个需要被处理的任务
- 这些任务可能是并行执行的,也可能是串行执行的
- 解决大并发的一个思路是将大任务分解成多个小任务:
- 以多进程/多线程并行的方式去执行这些小任务
- 以单进程/单线程配合多路复用执行这些小任务
- 以多进程/多线程并行的方式去执行这些小任务
上一篇: 在古代六部指的是什么?职责又是什么?