前言
以前没怎么接触前端,对 JavaScript 的异步操作不了解,现在有了点了解。一查发现 Python 和 JavaScript 的协程发展史简直就是一毛一样!
这里大致做下横向对比和总结,便于对这两个语言有兴趣的新人理解和吸收。
共同诉求
随着 cpu 多核化,都需要实现由于自身历史原因(单线程环境)下的并发功能
简化代码,避免回调地狱,关键字支持
有效利用操作系统资源和硬件:协程相比线程,占用资源更少,上下文更快
什么是协程?
总结一句话,协程就是满足下面条件的函数:
可以暂停执行(暂停的表达式称为暂停点)
可以从挂起点恢复(保留其原始参数和局部变量)
事件循环是异步编程的底层基石
混乱的历史
Python 协程的进化
Python2.2 中,第一次引入了生成器
Python2.5 中,yield 关键字被加入到语法中
Python3.4 时有了 yield from(yield from 约等于 yield + 异常处理 + send), 并试验性引入的异步 I/O 框架 asyncio(PEP 3156)
Python3.5 中新增了 async/await 语法(PEP 492)
Python3.6 中 asyncio 库"转正" (之后的官方文档就清晰了很多)
在主线发展过程中,也出现了很多支线的协程实现如 Gevent。
def foo():
print("foo start")
a = yield 1
print("foo a", a)
yield 2
yield 3
print("foo end")
gen = foo()
# print(gen.next())
# gen.send("a")
# print(gen.next())
# print(foo().next())
# print(foo().next())
# 在python3.x版本中,python2.x的g.next()函数已经更名为g.__next__(),使用next(g)也能达到相同效果。
# next()跟send()不同的地方是,next()只能以None作为参数传递,而send()可以传递yield的值.
print(next(gen))
print(gen.send("a"))
print(next(gen))
print(next(foo()))
print(next(foo()))
list(foo())
"""
foo start
1
foo a a
2
3
foo start
1
foo start
1
foo start
foo a None
foo end
"""
JavaScript 协程的进化
同步代码
异步 JavaScript: callback hell
ES6 引入 Promise/a+, 生成器 Generators(语法 function foo(){}* 可以赋予函数执行暂停/保存上下文/恢复执行状态的功能), 新关键词 yield 使生成器函数暂停。
ES7 引入 async函数/await语法糖,async 可以声明一个异步函数(将 Generator 函数和自动执行器,包装在一个函数里),此函数需要返回一个 Promise 对象。await 可以等待一个 Promise 对象 resolve,并拿到结果
Promise 中也利用了回调函数。在 then 和 catch 方法中都传入了一个回调函数,分别在 Promise 被满足和被拒绝时执行,这样就就能让它能够被链接起来完成一系列任务。
总之就是把层层嵌套的 callback 变成 .then().then()…,从而使代码编写和阅读更直观。
生成器 Generator 的底层实现机制是协程 Coroutine。
function* foo() {
console.log("foo start")
a = yield 1;
console.log("foo a", a)
yield 2;
yield 3;
console.log("foo end")
}
const gen = foo();
console.log(gen.next().value); // 1
// gen.send("a") // http://www.voidcn.com/article/p-syzbwqht-bvv.html SpiderMonkey引擎支持 send 语法
console.log(gen.next().value); // 2
console.log(gen.next().value); // 3
console.log(foo().next().value); // 1
console.log(foo().next().value); // 1
/*
foo start
1
foo a undefined
2
3
foo start
1
foo start
1
*/
Python 协程成熟体
可等待对象可以在 await 语句中使用,可等待对象有三种主要类型:协程(coroutine), 任务(task) 和 Future。
协程(coroutine)
协程函数:定义形式为 async def 的函数;
协程对象:调用 协程函数 所返回的对象
旧式基于 generator(生成器)的协程
任务(Task 对象):
任务 被用来“并行的”调度协程, 当一个协程通过 asyncio.create_task() 等函数被封装为一个 任务,该协程会被自动调度执行
Task 对象被用来在事件循环中运行协程。如果一个协程在等待一个 Future 对象,Task 对象会挂起该协程的执行并等待该 Future 对象完成。当该 Future 对象 完成,被打包的协程将恢复执行。
事件循环使用协同日程调度: 一个事件循环每次运行一个 Task 对象。而一个 Task 对象会等待一个 Future 对象完成,该事件循环会运行其他 Task、回调或执行 IO 操作。
asyncio.Task 从 Future 继承了其除 Future.set_result() 和 Future.set_exception() 以外的所有 API。
未来对象(Future):
Future 对象用来链接 底层回调式代码 和高层异步/等待式代码。
不用回调方法编写异步代码后,为了获取异步调用的结果,引入一个 Future 未来对象。Future 封装了与 loop 的交互行为,add_done_callback 方法向 epoll 注册回调函数,当 result 属性得到返回值后,会运行之前注册的回调函数,向上传递给 coroutine。
几种事件循环(event loop):
libevent/libev:Gevent(greenlet + 前期 libevent,后期 libev)使用的网络库,广泛应用;
tornado:tornado 框架自己实现的 IOLOOP;
picoev:meinheld(greenlet+picoev)使用的网络库,小巧轻量,相较于 libevent 在数据结构和事件检测模型上做了改进,所以速度更快。但从 github 看起来已经年久失修,用的人不多。
uvloop:Python3 时代的新起之秀。Guido 操刀打造了 asyncio 库,asyncio 可以配置可插拔的event loop,但需要满足相关的 API 要求,uvloop 继承自 libuv,将一些低层的结构体和函数用 Python 对象包装。目前 Sanic 框架基于这个库
Python 协程与 JavaScript 协程的比较
前言 以前没怎么接触前端,对 JavaScript 的异步操作不了解,现在有了点了解。一查发现 Python 和 JavaScript 的协程发展史简直就是一毛一样! 这里大致做下横向对比和总结,便于对这两个语言有兴趣的新人理解和吸收。 共同诉求 随着 cpu 多核化,都需要实
本文来自网络,不代表站长网立场,转载请注明出处:https://www.tzzz.com.cn/html/biancheng/yuyan/2021/1105/20288.html