前言

以前没怎么接触前端对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协程与JavaScript协程的对比的更多相关文章

  1. Python—进程、线程、协程

    一.线程 线程是操作系统能够进行运算调度的最小单位.它被包含在进程之中,是进程中的实际运作单位.一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务 方法: ...

  2. Python PEP 492 中文翻译——协程与async/await语法

    原文标题:PEP 0492 -- Coroutines with async and await syntax 原文链接:https://www.python.org/dev/peps/pep-049 ...

  3. python进程、线程、协程(转载)

    python 线程与进程简介 进程与线程的历史 我们都知道计算机是由硬件和软件组成的.硬件中的CPU是计算机的核心,它承担计算机的所有任务. 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资 ...

  4. Python进程、线程、协程详解

    进程与线程的历史 我们都知道计算机是由硬件和软件组成的.硬件中的CPU是计算机的核心,它承担计算机的所有任务. 操作系统是运行在硬件之上的软件,是计算机的管理者,它负责资源的管理和分配.任务的调度. ...

  5. python——进程、线程、协程

    Python线程 Threading用于提供线程相关的操作,线程是应用程序中工作的最小单元. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 #!/usr/bin/env pytho ...

  6. python单线程,多线程和协程速度对比

    在某些应用场景下,想要提高python的并发能力,可以使用多线程,或者协程.比如网络爬虫,数据库操作等一些IO密集型的操作.下面对比python单线程,多线程和协程在网络爬虫场景下的速度. 一,单线程 ...

  7. Python 线程和进程和协程总结

    Python 线程和进程和协程总结 线程和进程和协程 进程 进程是程序执行时的一个实例,是担当分配系统资源(CPU时间.内存等)的基本单位: 进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其 ...

  8. 消息/事件, 同步/异步/协程, 并发/并行 协程与状态机 ——从python asyncio引发的集中学习

    我比较笨,只看用await asyncio.sleep(x)实现的例子,看再多,也还是不会. 已经在unity3d里用过coroutine了,也知道是“你执行一下,主动让出权限:我执行一下,主动让出权 ...

  9. Python并发编程系列之协程

    1 引言 协程是近几年并发编程的一个热门话题,与Python多进程.多线程相比,协程在很多方面优势明显.本文从协程的定义和意义出发,结合asyncio模块详细讲述协程的使用. 2 协程的意义 2.1 ...

随机推荐

  1. C# 通过ServiceStack 操作Redis——Set类型的使用及示例

    ServiceStack 程序集里面没有方法注解,我在这里将注解添加上去,有不当之处,欢迎指正 Console.WriteLine("---Set类型---"); //添加 set ...

  2. POJ_1227 Jack Straws 【二维平面判两线段相交】

    一 题面 POJ1127 二 分析 在平面几何中,判断两线段相交的方法一般是使用跨立实验.但是这题考虑了非严格相交,即如何两个线段刚好端点相交则也是相交的,所以还需要使用快速排斥实验. 这里参考并引用 ...

  3. CPython-对象/类型系统

    Python中一切皆对象,包括实例对象和类型对象,如整数.浮点数.字符串是实例对象,整数类型.浮点数类型.字符串类型是类型对象. # [Python]>>> n=10 >> ...

  4. Android Studio 安装及配置

    安装时的那些事 •相关链接 [1]:无需翻墙的链接 [2]:Android Studio 安装教程 •从安装到放弃??? 初次接触 Android,并知道了开发 Android APP 的软件--An ...

  5. 面试高频题:说一说对Spring和SpringMvc父子容器的理解?

    引言 以前写了几篇关于SpringBoot的文章<面试高频题:springBoot自动装配的原理你能说出来吗>.<保姆级教程,手把手教你实现一个SpringBoot的starter& ...

  6. PAT (Advanced Level) Practice 1031 Hello World for U (20 分) 凌宸1642

    PAT (Advanced Level) Practice 1031 Hello World for U (20 分) 凌宸1642 题目描述: Given any string of N (≥5) ...

  7. [素数判断]P1125 笨小猴

    笨小猴 题目描述 笨小猴的词汇量很小,所以每次做英语选择题的时候都很头疼.但是他找到了一种方法,经试验证明,用这种方法去选择选项的时候选对的几率非常大! 这种方法的具体描述如下:假设maxn是单词中出 ...

  8. 【Python学习笔记】-虚拟环境virtualenv

    在开发python应用程序的时候,系统安装的python3只有一个版本:3.4.所有的第三方的包都回被pip安装到python3的site-packages目录下. 如果我们要要同时开发多个应用程序, ...

  9. 带你全面认识CMMI V2.0(二)

    CMMI V2.0成熟度等级 CMMI V2.0的一大变化是,所有实践领域均适用于成熟度三级(ML3),并具有特定的附加必需实践水平. 例如,在ML3上需要进行因果分析和解决,但在CMMI成熟度四级( ...

  10. 201871030106-陈鑫莲 实验三 结对项目—《D{0-1}KP 实例数据集算法实验平台》项目报告

    项目 内容 课程班级博客链接 班级博客 这个作业要求链接 作业要求 我的课程学习目标 1.学会结对学习,体会结对学习的快乐2.了解并实践结对编程 3.加深对D{0-1}问题的解法的理解4.复习并熟悉P ...