从Javascript到Python,再看asyncio
,我有点困惑。
作为一个对并发基本概念还不熟悉的人,我只是假设对Javascript并发有一个肤浅的理解。
在Javascript中使用async / await
的基本理解:
如果我们在async
函数内运行任何进程,而await
是函数的响应,那么我们本质上是在等待函数在Promise
上设置一个值。
完全有意义——当给Promise
一个值时,我们还可以使用回调(如.then()
)来处理响应。或者,只使用await
。
无论异步性的底层实现是什么(例如,所有进程都运行在带有事件循环的单个线程上),我们如何与它接口是否重要?
现在,我转到Python并开始使用asyncio
。我们有Futures
,就像Promises
一样。突然之间,我不能使用我的标准库,如request.get(...)
,但我需要在aiohttp
等库中使用非阻塞网络请求。
阻塞/非阻塞在这里是什么意思?我认为这意味着事件循环所在的单个线程被阻塞,因此我们无法并行处理其他函数。
所以我的两个问题是:
- 是什么原因导致单个线程被阻塞?例如
requests.get(...)
- 为什么大多数函数在Javascript中是非阻塞的,而在Python中不是(即我们不需要
aiohttp
等特定库)
像Go
这样的语言及其goroutines
呢?这只是一种情况吗?因为它是一种从一开始就内置了并发性的新语言,所以阻塞函数的概念并不存在。或者在Go中,它不是一个单一的线程,所以一切都可以本质上平行化?
谢谢:)
事件循环
Javascript和python的async io使用了基于事件循环的并发模型。
(注意复数,因为您可以有多个事件循环来处理不同类型的任务,例如磁盘io、网络io、ipc、并行计算等)
事件循环的一般概念是,你有很多事情要做,所以你把这些事情放在一个队列中,每隔一段时间(比如每纳秒),事件循环就会从队列中选择一个事件,并运行一小段时间(可能是一毫秒左右),如果它还没有完成,就把它推回到队列中,或者等待,直到它把控制权交还给事件循环。
现在回答您的一些问题:
阻塞/非阻塞在这里意味着什么?我想这意味着事件循环所在的单个线程被阻塞,因此我们无法处理并行的其他功能。
阻塞事件循环
当事件循环正在运行任务,并且任务尚未完成或将控制权交还给事件循环时,会发生阻塞事件循环的情况,时间比事件循环计划运行的时间长。
在python的请求库的情况下,它们使用同步http库,该库不尊重事件循环;因此,在循环中运行这样的任务将使耐心等待轮到它们运行的其他任务挨饿,直到请求完成。
为什么大多数函数在Javascript中是非阻塞的,而在Python中不是(即,我们不需要特定的库,如
aiohttp
)。
JS
Javascript中的所有内容都可以阻止事件循环。不阻塞事件循环的唯一方法是通过setTimeout大量使用回调。但是,如果不小心,即使是那些回调,如果运行时间过长,也可能会阻塞事件循环,而不会通过另一个setTimeout
调用将控制权交回事件循环。
(如果你从未使用过setTimeout
,但在JS中使用过promise和异步网络请求,那么你可能正在使用这样的库。浏览器中使用的大多数流行的网络库(ajax、axios、fetch等)都基于流行的XMLHttpRequest
API,它提供异步网络IO。)
Python
在python中,故事略有不同:在asyncio之前,没有像"事件循环";。在python解释器进入下一步之前,所有的事情都必须运行完成。这是python非常容易学习的部分原因(我敢说,创建…)。原因是python GIL的形式,简单来说,它强制执行任何python程序的单一执行顺序。我鼓励您点击该链接,阅读GIL存在的原因。
像
Go
这样的语言及其goroutines
呢?
注意:我不是围棋程序员,但我读了一些
Go有何不同
go处理goroutines的方式和python asyncio/js执行事件循环的方式之间的唯一区别是,go更多地使用os线程,以确保线程得到公平调度,并充分利用它们运行的机器。
虽然js回调/asyncio任务通常与事件循环在同一个线程中运行,但goroutines能够在单独的操作系统线程中运行并在多个内核上运行,从而提供更高的可用性和更高的并行性(在这种情况下,与受事件循环线程运行时间限制的绿色线程相比,我们几乎可以认为goroutines在实际运行时间方面更接近OS线程。)