前言

以前没怎么接触前端对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 start1foo a a23foo start1foo start1foo startfoo a Nonefoo 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); // 2console.log(gen.next().value); // 3console.log(foo().next().value); // 1console.log(foo().next().value); // 1/*foo start1foo a undefined23foo start1foo start1*/

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 asyncioimport timeasync 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

总结与比照

阐明pythonJavaScript点评
过程单过程单过程统一
中断/复原yield ,yield from,next,sendyield ,next基本相同,但 JavaScript 对 send 没啥需要
将来对象(回调包装)FuturesPromise解决callback,思路雷同
生成器generatorGenerator将yield封装为协程Coroutine,思路一样
成熟后关键词async、awaitasync、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()