共计 5174 个字符,预计需要花费 13 分钟才能阅读完成。
前言
以前没怎么接触前端对 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 框架基于这个库
例子
import asyncio
import time
async def exec():
await asyncio.sleep(2)
print('exec')
# 这种会和同步成果始终
# async def go():
# print(time.time())
# c1 = exec()
# c2 = exec()
# print(c1, c2)
# await c1
# await c2
# print(time.time())
# 正确用法
async def go():
print(time.time())
await asyncio.gather(exec(),exec()) # 退出协程组对立调度
print(time.time())
if __name__ == "__main__":
asyncio.run(go())
JavaScript 协程成熟体
Promise 持续应用
Promise 实质是一个状态机,用于示意一个异步操作的最终实现 (或失败), 及其后果值。它有三个状态:
- pending: 初始状态,既不是胜利,也不是失败状态。
- fulfilled: 意味着操作胜利实现。
- rejected: 意味着操作失败。
最终 Promise 会有两种状态,一种胜利,一种失败,当 pending 变动的时候,Promise 对象会依据最终的状态调用不同的处理函数。
async、await 语法糖
async、await 是对 Generator 和 Promise 组合的封装, 使原先的异步代码在模式上更靠近同步代码的写法, 并且对错误处理 / 条件分支 / 异样堆栈 / 调试等操作更敌对.
js 异步执行的运行机制
1) 所有工作都在主线程上执行,造成一个执行栈。
2) 主线程之外,还存在一个 ” 工作队列 ”(task queue)。只有异步工作有了运行后果,就在 ” 工作队列 ” 之中搁置一个事件。
3) 一旦 ” 执行栈 ” 中的所有同步工作执行结束,零碎就会读取 ” 工作队列 ”。那些对应的异步工作,完结期待状态,进入执行栈并开始执行。
遇到同步工作间接执行, 遇到异步工作分类为宏工作 (macro-task) 和微工作 (micro-task)。
以后执行栈执行结束时会立即先解决所有微工作队列中的事件,而后再去宏工作队列中取出一个事件。同一次事件循环中,微工作永远在宏工作之前执行。
例子
var sleep = function (time) {console.log("sleep start")
return new Promise(function (resolve, reject) {setTimeout(function () {resolve();
}, time);
});
};
async function exec() {await sleep(2000);
console.log("sleep end")
}
async function go() {console.log(Date.now())
c1 = exec()
console.log("-------1")
c2 = exec()
console.log(c1, c2)
await c1;
console.log("-------2")
await c2;
console.log(c1, c2)
console.log(Date.now())
}
go();
event loop 将工作划分:
- 主线程循环从 ” 工作队列 ” 中读取事件
- 宏队列(macro task)js 同步执行的代码块,setTimeout、setInterval、XMLHttprequest、setImmediate、I/O、UI rendering 等, 实质是参加了事件循环的工作.
- 微队列(micro task)Promise、process.nextTick(node 环境)、Object.observe, MutationObserver 等, 实质是间接在 Javascript 引擎中的执行的没有参加事件循环的工作.
扩大浏览 Node.js 中的 EventLoop
总结与比照
阐明 | python | JavaScript | 点评 |
---|---|---|---|
过程 | 单过程 | 单过程 | 统一 |
中断 / 复原 | yield ,yield from,next,send | yield ,next | 基本相同, 但 JavaScript 对 send 没啥需要 |
将来对象(回调包装) | Futures | Promise | 解决 callback, 思路雷同 |
生成器 | generator | Generator | 将 yield 封装为协程 Coroutine, 思路一样 |
成熟后关键词 | async、await | async、await | 关键词反对, 一毛一样 |
事件循环 | asyncio 利用的外围。事件循环会运行异步工作和回调,执行网络 IO 操作,以及运行子过程。asyncio 库反对的 API 较多, 可控性高 | 基于浏览器环境根本是黑盒, 内部根本无法控制, 对工作有做优先级分类, 调度形式有区别 | 这里有很大区别, 运行环境不同, 对工作的调度先后不同, Python 可能和 Node.js 对于事件循环的可比性更高些, 这里还需须要持续学习 |
到这里就根本完结了, 看完不晓得你会有什么感想, 如有谬误还请不吝赐教.
顺便一句,Python 的 asyncio 库的官网文档当初清晰了很多, 而不是像 Python3.5 前的文档让人看不懂更无从下手, 心愿前面能有更多的第三方库跟进, 开发出更多反对异步的库. 毕竟目前和 go 原生协程比起来还是有差距的.
参考
- python asyncio 五颗星
- 从 event loop 到 async await 来理解事件循环机制 五颗星
- JS 的事件轮询 (Event Loop) 机制 五颗星
- JavaScript 中的协程 五颗星
- JavaScript yield next send
- JavaScript 迭代器和生成器
- JavaScript Promise
- Python 异步历史
- Python 协程技术的演进
- JS 中的协程(Coroutine)
- Node.js 事件循环,定时器和 process.nextTick()