学到这里,很多人都会遇到一个非常真实的问题。
程序能写了,功能也能实现了,可一旦任务多起来,速度就开始不对劲。
比如你要同时下载多个文件。 比如你要批量请求很多网页。 比如你要一边处理数据,一边等数据库返回结果。 比如你做自动化脚本时,明明每一步都不复杂,可整个程序执行起来就是特别慢。
这时候,你就会接触到几个高频词:
同步 异步 并发 并行
很多初学者第一次看到这些词,会觉得特别绕。明明都和多任务有关,为什么还要分这么细。更麻烦的是,这几个词经常一起出现,一不小心就混了。
这一章,我们不急着写复杂代码,先把这几个概念真正讲明白。因为后面学多线程、多进程、协程时,能不能学顺,很大程度上取决于你现在能不能把这些基础概念分清。
一、什么叫多任务
先不要急着看同步异步,先把多任务这三个字理解透。
所谓多任务,说白了,就是程序不只做一件事,而是要处理多件事。
比如一个下载工具,要下载 10 个文件。 比如一个爬虫,要抓取 100 个页面。 比如一个聊天软件,要一边接收消息,一边发送消息。 比如一个办公脚本,要同时读取很多文档,再分别整理输出。
这些都属于多任务场景。
注意,这里的多任务,不一定真的是同一时刻做很多事。它更准确的意思是,程序要面对多个任务,而不是只干一件单线程的小事。
理解这一点很重要。因为后面你会发现,有的多任务是轮流做的,有的多任务是真的同时做的,这就引出了并发和并行的区别。
二、先从最容易理解的同步开始
同步,其实是我们最熟悉的一种执行方式。
它的特点很简单:
一件事做完,再做下一件事。 前一步没结束,后一步就得等着。
比如下面这段代码:
import timedeftask1(): print("开始任务1") time.sleep(2) print("任务1结束")deftask2(): print("开始任务2") time.sleep(2) print("任务2结束")task1()task2()
执行顺序一定是这样的:
先执行任务1 等任务1彻底做完 再去执行任务2
这就是同步。
同步最大的特点,不是慢,而是顺序明确。它像排队办事,一个窗口一个人,前面没办完,后面就不能插进来。
这种方式的优点是逻辑简单,代码容易理解,排查问题也方便。缺点也很明显,只要某一步卡住了,后面的任务就全都得等。
所以,同步最适合那种步骤之间依赖很强、必须按顺序推进的场景。
比如:
先登录,再获取个人信息 先读取配置,再执行任务 先生成数据,再写入文件
这些场景本来就有前后依赖,用同步写最自然。
三、同步为什么会让人觉得慢
同步真正让人难受的地方,不是做事本身,而是等待。
举个例子。
假设你要请求 3 个网页,每个网页响应都需要 2 秒。如果用同步方式写,大概就是这样:
import timedefrequest_page(name): print(f"{name} 开始请求") time.sleep(2) print(f"{name} 请求结束")request_page("页面1")request_page("页面2")request_page("页面3")
总耗时大概是 6 秒。
为什么这么久?
因为这 3 个任务是一个一个做的。页面1等完了,才能轮到页面2,页面2结束了,页面3才开始。程序在等待页面返回数据的时候,其实什么别的事都没做。
这就是同步模式最典型的性能浪费。
不是工作太难,而是等的时候太老实了。
四、异步到底是什么意思
很多人看到异步这两个字,第一反应是:
是不是异步就是同时执行。
这其实不够准确。
异步更本质的意思是:
当程序遇到需要等待的操作时,不傻等,先去做别的事,等时机到了再回来继续处理。
注意,这里最关键的词,不是同时,而是不要堵住。
举个生活化的例子。
你去餐厅点餐。
同步像什么呢。你点完一份炒饭,然后站在厨房门口一直等,厨师没做好之前,你什么都不干。
异步像什么呢。你点完炒饭之后,先去找座位,顺便拿餐具、倒水、看消息。等饭好了,服务员再叫你。
这就是异步。
异步并不一定意味着多个任务真正同时在跑,它强调的是:遇到等待时,不要把时间白白浪费掉。
所以,异步特别适合那种等待时间很多的场景,比如网络请求、磁盘读写、数据库访问、消息收发。
五、同步和异步,核心区别到底在哪
你可以把它们的差别记成一句很直白的话:
同步是等着做 异步是先去做别的,回头再处理
再说得具体一点。
同步模式下,任务执行到某一步,如果这里要等,那整个流程就暂停在这里。
异步模式下,任务执行到某一步,如果这里要等,程序会把这个等待先挂起来,然后去推进别的任务。等条件满足,再回来接着做。
也就是说,异步的价值主要体现在等待型任务里。
如果任务本身没什么等待,而是一直在疯狂计算,那异步未必能帮你多少。因为它能优化的是等待时间,不是纯计算能力。
这一点你一定要提前记住。后面很多人学协程效果不理想,本质上就是任务类型没搞对。
六、再说并发,这个词最容易被误解
并发是很多人最容易混淆的概念。
你经常会听到一种说法:
并发就是多个任务一起做。
这话不能说错,但太粗糙了。
更准确一点,并发指的是:
在一段时间内,多个任务都在推进。
这里注意,是在一段时间内都在推进,不一定是某一个绝对瞬间真正同时执行。
举个例子。
你有 3 个任务要做:
回邮件 整理表格 下载资料
如果你是这样处理:
先回一点邮件 等下载时,切去整理表格 表格处理中间又去看一眼邮件 再回头继续下载任务
这种感觉,就是并发。
多个任务交替推进,谁也不是从头到尾一口气做完,但整体都在往前走。
所以,并发的重点是任务管理上的重叠,而不是物理意义上的绝对同时。
七、并发不等于并行
这是必须分清的一组概念。
很多人把并发和并行当成一个东西,其实它们不一样。
并发强调的是:
多个任务看起来是一起推进的。
并行强调的是:
多个任务在同一时刻真的同时执行。
这两个词最大的区别在于,是否真的同一时间一起跑。
举个简单例子。
你一个人做三件事,一会儿看锅,一会儿切菜,一会儿洗菜。这叫并发。因为这些事在同一段时间里都在推进,但你毕竟只有一个人,不可能同一秒同时切菜和洗菜。
如果你和另外两个人一起做饭,一个人切菜,一个人洗菜,一个人看锅,这就更接近并行。因为不同任务真的在同一时刻由不同执行单元同时进行。
所以你可以这样理解:
并发像一个人来回切换处理多件事。 并行像多个人同时各干各的事。
这个比喻虽然不完全等价,但对初学者来说非常好理解。
八、为什么单核 CPU 也能实现并发
很多人听到并发之后,会问一个问题:
如果电脑只有一个核心,怎么可能同时处理多个任务。
答案是,可以通过快速切换来实现。
CPU 虽然同一时刻只能执行一个任务,但它切换得非常快。快到人几乎感觉不出来,于是你就会觉得多个任务像是在一起进行。
就像你看动画片,其实一张张图片是连续切换出来的,只不过切换速度够快,你就以为它动起来了。
程序里的并发,很多时候也是这个道理。
多个任务并不是每一瞬间都真的一起跑,而是 CPU 在它们之间快速来回切换,于是从整体效果上看,多个任务都在推进。
这也是为什么并发和并行不能混为一谈。
并发可以只靠调度和切换实现。 并行通常需要多个执行单元,比如多核 CPU。
九、并行到底适合什么场景
并行最适合的,是那种可以拆开、彼此独立、而且计算量比较大的任务。
比如:
大批量图片处理 视频转码 复杂数学运算 大规模数据计算
这类任务如果能拆成多个子任务,交给多个核心同时处理,整体速度就可能明显提升。
举个简单理解。
假设你有 1000 张图片要缩放。
同步做法是,一张一张处理。 并发做法可能是任务在不同处理流程之间切换推进。 并行做法则是把图片分给多个执行单元,同时处理不同部分。
因为图片之间互不依赖,所以很适合并行。
但如果任务之间前后依赖特别强,比如必须先做 A 再做 B,再做 C,这种场景并行效果就有限了。
所以并行不是万能加速器,它也要看任务本身能不能拆。
十、同步、异步、并发、并行,这四个词到底是什么关系
学到这里,我们把这四个概念重新整理一遍。
同步和异步,描述的是任务执行时,遇到等待该怎么处理。
同步是等当前任务做完,再继续。 异步是当前任务要等时,先去推进别的任务。
并发和并行,描述的是多个任务之间的推进关系。
并发是在一段时间内,多个任务都在推进。 并行是在同一时刻,多个任务真的同时执行。
这两组概念不是互相排斥的,而是可以组合出现。
比如:
异步并发 多线程并发 多进程并行
也就是说,同步异步是一组维度,并发并行又是另一组维度。
很多初学者之所以觉得乱,就是因为把两套分类混在一起理解了。
你只要记住:
同步异步,关注的是等不等。 并发并行,关注的是是不是一起推进、是不是同时执行。
脑子里一下就清楚多了。
十一、一个特别容易混淆的误区
很多人会说:
异步就是并发。 多线程就是并行。 程序只要用了异步就一定快。
这些说法都不严谨。
先说第一句,异步就是并发。
不完全对。异步通常可以帮助实现并发效果,但异步本身强调的是等待时不阻塞,而并发强调的是多个任务在一段时间内共同推进。两者关系密切,但不是一个概念。
再说第二句,多线程就是并行。
也不一定。多线程有时候是并发,有时候能接近并行效果,得看具体语言机制、运行环境和任务类型。不能一概而论。
最后一句,异步一定快。
这更不对。异步只是在适合它的场景里更高效。比如网络请求很多、等待时间很多,它可能非常有优势。但如果是大量纯计算任务,异步不一定能帮上忙。
所以以后看到这些概念,别急着直接和速度划等号。先看任务是什么,再看工具适不适合。
十二、一个最通俗的总比喻
如果你还是觉得抽象,那我用一个生活里的场景,把它们一起讲透。
假设你要洗衣服、做饭、打扫卫生。
同步是什么。 先洗衣服,洗完再做饭,做完再打扫。一步一步来。
异步是什么。 你先把衣服塞进洗衣机开始洗,机器转的时候你去切菜,锅在炖的时候你去扫地。谁需要等待,就先放一边,去做别的。
并发是什么。 在这一小时里,洗衣、做饭、打扫这几件事都在推进。虽然你没有真正同一秒干三件事,但整体都在往前走。
并行是什么。 你、家人、扫地机器人同时分别干不同的活,多个任务真的同一时刻一起进行。
你会发现,这样一对照,几个概念一下就立住了。
十三、为什么学并发编程前,必须先搞懂这些概念
因为后面你一旦开始学 threading、multiprocessing、asyncio,如果概念没打稳,很容易出现两种情况。
第一种,写代码全靠模仿。
照着教程写,代码能运行,但你不知道自己为什么这样写,也不知道这种方式适合什么问题。
第二种,乱用工具。
本来是网络请求任务,结果用错了方式。 本来是计算密集任务,结果还在指望异步提速。 本来只是一个简单脚本,结果为了追求高级感,硬是加了一堆复杂结构。
最后代码变难维护,性能也没见多大提升。
所以并发编程最重要的,不是先学语法,而是先建立判断力。
你要先知道:
这是等待多,还是计算多 这是适合交替推进,还是适合真正同时执行 这是该用同步,还是该考虑异步
思路对了,后面的技术才有意义。
十四、看一组最简单的感受型代码
先看同步执行:
import timedeftask(name): print(f"{name} 开始") time.sleep(2) print(f"{name} 结束")start = time.time()task("任务1")task("任务2")task("任务3")print("总耗时:", time.time() - start)
这个程序大概率要 6 秒左右。
因为 3 个任务是一个个排队做的。
再看一个伪代码层面的异步思路,你现在不用完全掌握写法,先体会意思就行:
任务1 发起等待任务2 发起等待任务3 发起等待谁先完成,就先处理谁的结果
如果这 3 个任务本质上都主要在等,比如都在等网络响应,那么总耗时就可能接近 2 秒多一点,而不是 6 秒。
注意,这里节省的不是计算时间,而是把原本傻等的时间利用起来了。
这就是为什么异步和并发在 I/O 场景下往往很有价值。
十五、本章你一定要记住的四句话
学完这一章,你至少要把下面四句话记牢。
同步:前面的不结束,后面的不开始。 异步:遇到等待时,不堵着,先去做别的。 并发:多个任务在一段时间内都在推进。 并行:多个任务在同一时刻真的同时执行。
如果这四句话你已经能顺口说出来,说明你的基础已经打下来了。
后面学多线程、多进程、协程时,你会轻松很多。
十六、动手练习
这一章最适合做的练习,不是马上上复杂框架,而是先训练概念判断。
你可以试着判断下面几个场景更接近什么。
场景一:你依次读取 3 个文件,前一个读完才读下一个。 这是同步。
场景二:你发出多个网络请求,在等待返回时去处理别的请求。 这是异步,也形成了并发效果。
场景三:一个 CPU 核心快速切换执行多个任务。 这是并发,不一定是并行。
场景四:4 个 CPU 核心同时处理 4 段独立计算。 这是并行。
再进一步,你可以想想自己以前写过的程序,哪些其实是同步顺序执行,哪些地方如果任务之间互不依赖,就有机会改造成并发处理。
当你开始用这种眼光看代码时,你对程序执行方式的理解就已经升级了。
十七、写在最后
很多初学者一接触并发编程,就急着记模块、背写法、照着教程敲代码。
其实最应该先学的,从来不是工具,而是概念。
因为工具会换,库会更新,写法会变,但背后的执行逻辑不会变。
你只有先明白:
什么叫同步 什么叫异步 什么叫并发 什么叫并行
后面学线程、进程、协程时,才不会每次都像在背天书。
编程里最怕的,不是暂时不会写,而是概念一直模糊。 一旦概念清楚了,代码只是迟早的事。