前言以前没怎么接触前端,对 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 异步执行的运行机制 所有任务都在主线程上执行,形成一个执行栈。
主线程之外,还存在一个任务队列(task queue)。只要异步任务有了运行结果,就在任务队列之中放置一个事件。
一旦执行栈中的所有同步任务执行完毕,系统就会读取任务队列。那些对应的异步任务,结束等待状态,进入执行栈并开始执行。
遇到同步任务直接执行,遇到异步任务分类为宏任务(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 引擎中的执行的没有参与事件循环的任务
总结与对比 说明 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 关于事件循环的可比性更高些,这里还需需要继续学习
到这里就基本结束了,看完不知道你会有什么感想,如有错误还请不吝赐教。
原文链接:https://www.cnblogs.com/lgjbky/p/14759463.html
如何用铅笔判断电视机内有无高压
AI给生活带来了什么便利
让你随时随地都能知道天气情况的传感器
关于RFID的仓库管理系统的方案设计
韩国5G市场的运营现状与发展战略分析
Python协程与JavaScript协程的对比及经验技巧
Creaform形创HandySCAN MAX l Elite三维扫描仪便携式3D测量解决方案
三星可卷曲屏幕再爆黑科技:搭载类似3D Touch重力按压技术
PCB设计之电气(Electrical)规则设置
SD-WAN对您的物联网发展至关重要的3个原因
700MHZ下的5G通信市场,信号增强器的发展空间在哪里
PCBNavigator在orcad与powerpcb间的应用说明
单片机系统硬件抗干扰的常用方法和单片机自身的抗干扰措施详细介绍
英特尔正在积极推动构建云网融合的边缘基础设施
小体积无线收发模块
畜牧业RFID电子耳标读卡器品牌选哪个好?怎么选?
创新不止步!软件成为皓丽会议平板硬实力
看到华为P10的海报,我觉得苹果危险了
触摸屏为什么能对人体触摸作出反应,它的原理是什么
如何挑选无线路由器以真正发挥WIFI的传送速率