zoukankan      html  css  js  c++  java
  • Python协程与JavaScript协程的对比

    前言

    以前没怎么接触前端对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关于事件循环的可比性更高些,这里还需需要继续学习

    到这里就基本结束了,看完不知道你会有什么感想,如有错误还请不吝赐教.

    参考

  • 相关阅读:
    FireGestures 火狐手势插件 使用
    计算分段采样区间中的平均值,标准差,中位数,积分值等的类
    DWR与Spring结合
    项目总结
    在线机器学习算法及其伪代码
    Hdu 1394 Minimum Inversion Number、Poj 2299 UltraQuickSort
    Ubuntn 安装sendmail并把硬盘空间信息发送到指定邮箱
    iPhone应用程序开发使用Core Data (一)
    [置顶] C++里被人遗忘的智能指针
    HTML标签p和div的不同
  • 原文地址:https://www.cnblogs.com/lgjbky/p/14759463.html
Copyright © 2011-2022 走看看