并发vs并行
并发是同时处理很多事情,并行是同时做很多事情。
并发是同时处理很多事情,有时间段的概念,这些事情可能会有一个先后顺序,但是会在这个时间段内去做。从这个时间段来看,这些事情都被处理了。
比如我在编写代码的同时还在听着音乐,这两件事情如果被调度在同一个CPU上,它们是并发执行的。
并行是在同一个时间点有多件事情都在做。如果手头阔绰,我还可以买一台机器放在旁边,打开Markdown编辑器编写文章的内容,同时音乐播放器在播放抖音上最流行的歌曲。这两台服务器可以并行地执行,在同一个时刻,两台服务器都在做着事情,这是并行。
并发和并行并不相同,但是相关。如图,左边并发,右边并行。
并发和并行区别的第一条已经讲得很清楚了,是同时“处理”和同时“做”的区别。例如,我很久没有去银行办理业务了,最近去了一次,其效率基本上和以前一样。进门后,大堂经理检查我的身份证和银行卡,然后在取号机上取了一个号给我,我的业务正式开始被“处理”了。我环顾四周,发现大堂里全是已被“处理”但是还在等待窗口办理的顾客。有限的几个窗口正在“做”业务,每个“做”的过程都非常漫长,有可能需要等半天才能轮到我“做”业务。这一点和计算机的CPU也是类似的,我使用的CPU可能只有几个核,但是我打开窗口,所做的事情却非常多,很多事情都在“被处理”,而正在“做”的工作也就是几个而已。银行办理业务是并发执行的,但是并行执行的就寥寥几个业务。
并发的焦点是设计结构,并行的焦点是程序的执行。 并发的本质是我们要设计/实现一个结构,这个结构可以使程序的不同计算模块并发地执行。这些模块的执行可能真的是并行的,比如在多核的CPU上,不同的模块不会相互阻塞,它们被分配到不同的核上,所以可以并行地执行。而在一个核的CPU上,它们不能并行地执行,但是可以并发地执行,在一段时间内每个模块都可以占用CPU的时间片,每个模块都可以被执行。并发编程的本质就是要设计/实现这样的程序结构,以便不同的模块可以并发地执行。并发的目标之一就是能利用并行(多核)的能力,但是并行的目标并不是并发。
以连锁饭店为例来介绍并发技术。
一家知名的美食饭店,专做八大菜系,在美食界赫赫有名。现在它准备在京师最火的美食街开一家分店。开店初期只租了一个场地,服务员、厨师和结账员都由店长一人承担。所以,一旦有一个顾客过来,店长就得亲自接待,安排好顾客座位,等待顾客点餐,然后拿着顾客的单子去炒菜,给顾客上菜,结账送客,接下来才能迎接下一个顾客。可想而知,店门外等待品尝美食的顾客排了长长的一队,但是由于此店是顺序接待顾客的,所以大家只能顺序就餐,前一个顾客吃完才能轮到下一个顾客。
某一天老板过来巡视,发现不行,这样太低效了,处理流程需要改造,于是把整个就餐流程改造成四部分:接待顾客和点餐、炒菜、上菜和结账。接待顾客和上菜可以由一个服务员负责,炒菜由一个厨师负责,然后结账由收银员负责,三个步骤可以并发地执行。它们之间可以通过消息传递信息,比如服务员递一个单子,厨师就知道要炒新菜了,厨师炒好菜后摇一下铃就可以通知服务员端菜了。服务员和收银员之间也通过打招呼的方式结账。接待顾客和点餐、炒菜、上菜和结账并发地执行,可以同时服务多个顾客,处理流程大大加快,店里的流水也多起来了,客栈开始盈利。
某一天老板又过来了,还是有点不满意,因为他发现同时就餐的顾客虽然多了,但是只有一个厨师,导致大部分顾客都在座位上苦苦等待,顾客颇有怨言;厨师在灶台前忙得热火朝天,而服务员坐在长凳上晒太阳,收银员在柜台后无聊地追剧,非常不合理。得益于先前的并发流程的改造,老板决定再增加7个厨师和7个灶台,厨师可以并行炒菜;再增加两个服务员,这样即使菜炒得很快,服务员也能及时端给顾客。因为顾客很快能吃到饭菜,所以就餐时间也很短,收银员也忙碌起来,一派欣欣向荣的景象。所以大家看到并发编程的设计,可以轻松地解决规模扩大的问题,并且可以利用并行的方式,同时处理多个并发单元。
小店随时间推移越来越有名气,客流也越来越大,即使采用了并发技术,也难以应对这么大的客流,顾客又不满意了。老板过来考察后,决定在这条美食街再开一家分店,照搬第一家分店的处理流程和资源配备,依然火爆,结果开了第三家分店、第四家分店。。。直到第八家分店,基本上满足了顾客的需求,顾客不需要再等待了。这就是采用并行的方式来解决问题,每个店的处理流程依然是并发的方式,服务员忙忙碌碌,厨师干得热火朝天,收银员手打算盘噼里啪啦,顾客不需要等待。
可以看到,并发是对结构的设计和改造,将整个处理流程拆解成可以并发处理的单元。拆解的方式也不是唯一的,还可以增加洗菜工、刷碗工、收拾餐余的工作人员等。总之,看到哪里有瓶颈,就要考虑有没有可能把它分解并发地执行。这些人员之间可以通过消息传递信息,如果同时有很多顾客结账,而这里只设置了一个收银员,还需要互斥锁等方式,将并发单元排成队列,顺序结账。如果结账这里一直是瓶颈的话,那么可以通过增加几个收银员来消除这个流程上的瓶颈。
综上所述,我们在设计并发程序的时候,经常要进行并发单元的设计,并且要进行并发模块之间的数据同步和消息传递,甚至要编排任务让它们按照固定的流程执行。在大部分场景下,并发程序可以提升程序的性能,而我们可以通过提供更多的CPU核等方式,让并发单元能够更多、更快地执行---尽管这不是必需的。
- Golang锁-Mutex的用法1/9/2026
- 检查Golang程序中的数据竞争1/12/2026
- Go并发并不一定最快1/6/2026
- 竞争条件与数据竞争1/8/2026
- 阿姆达尔定律:并发编程优化是有上限的1/5/2026
- 适合并发编程的语言Golang1/2/2026