zoukankan      html  css  js  c++  java
  • 协程 的 实现方法

    可以先看看我之前写的  《再见 异步回调, 再见 Async Await, 10 万 个 协程 的 时代 来 了》   https://www.cnblogs.com/KSongKing/p/10802278.html   ,

     

    要实现 协程 ,  需要 语言 自己 实现一个 线程池,  然后 把 协程 放到 线程 里 执行  。

     

    协程 切换 就是 把 当前 协程 的 栈顶 和 指令计数器 保存起来 ,  在 操作系统 里,   栈顶 和 指令计数器 就是 线程  上下文, 由 操作系统 保存,

    对于  协程,   语言 可以 在 堆 里 创建一个 协程 对象,  协程 对象 有 一个 栈顶 字段 和 一个 指令计数器 字段,

    协程 的 栈顶 和 指令计数器 就可以保存到 栈顶 字段 和 指令计数器 字段 里,

    到 下次 轮到 这个 协程 执行时,  再 把 栈顶 字段 和 指令计数器 字段 的 值 赋值给 寄存器,

    这样 就可以 实现 协程 的 切换 了 。

     

    和 操作系统 类似,   语言 需要 维护 一个 协程 的 就绪队列 和 一个 挂起队列 ,    协程 在 等待 其它 协程 或者 线程 时, 需要 挂起, 这样 协程 对象 会 添加到 挂起队列,  当 其它 协程 或 线程 完成时, 会 将 协程 从 挂起 队列 移除, 添加 到 就绪队列, 这样 不久的将来 就可以 继续 执行 协程  。

     

    对 就绪队列 和 挂起队列 的 添加 和 移除 操作 是一个 队列 的 操作,   考虑到 并发,  需要在 添加 和 移除 时 Lock(同步 / 互斥),  这可以使用 CAS lock ,  而不是 操作系统 提供的 lock,     CAS 的 性能 更高,    new 操作 应该 就是 使用 CAS lock 来 修改 堆 表,   这样的话,  协程 挂起 和 唤醒 的 时间花费 和 new 操作 差不多  。

     

    总的来说,   协程 切换 的 时间花费 相当于 执行了 一段 很短 的 代码   。

     

    为什么要 语言 自己实现一个 线程池?   而不是 使用 操作系统 提供 的 线程池,   因为 使用 操作系统 提供的 线程池 就成了   async await   了   。 ^^

     

    这又涉及到  协程 和  async await   的  区别 的 问题,    这个问题 和   为什么 要 语言 自己 实现一个 线程池 差不多 是 同一个 问题  。

     

    操作系统 的 线程池 的 任务单位 是一个 函数,   所以,   async await  使用 操作系统 的 线程池 的 话,  就需要 把 一个 方法 内 跨线程 调用 前 和 后 的 两部分 代码 切割 为 2 个 函数 ,    在 语言 的 层面 ,  这 2 个 函数 本身是一个 方法(函数),   所以 函数 2 可以 访问 函数 1 中 的 变量,   但 既然 切割 成了 2 个 函数 , 那么 要 让 函数 2 可以 访问 函数 1 中 的 变量,  就需要      闭包  状态机     等 方式 来 共享  这些 变量   。

     

    对于 协程 ,    由于 是 自己 实现 的 线程池 ,   所以 把 协程 放在 线程 里 执行 不需要 以 函数 为 单位,  而是 载入 协程 的 上下文(栈顶  指令计数器) 到 寄存器, 然后 继续 执行 指令 就可以,  这些是 语言 在 汇编 层面 完成的,   就是说 是 语言 的 编译器 在 生成 目标代码(汇编)时 将 代码 编译 成 这样的  。

     

    所以,   对于 async await  来说, 把 一个 方法 切割成 函数1 和 函数2   两部分 ,    那么 执行 函数1 和 函数2 时 ,   两者 有 各自 的 栈顶 ,   是 2 次 函数调用,

    而 对于 协程,   跨协程 / 跨线程  调用 前 后 的 2 部分 代码 在 执行时 的 栈顶 是 同一个,  是一次 完整的 函数调用  。      只不过 在 跨协程 / 跨线程  调用 时   发生了 “切换”  ,     即把 寄存器 保存的 栈顶 作为 上下文 保存到 协程 对象 里 ,    当 切换回来 时 再 从 协程 对象 的 栈顶 字段 赋值 给 寄存器 ,   然后 接着 执行  。

    挂起前 和 恢复执行后 的  栈顶 是 同一个,    或者说,   跨协程 / 跨线程  调用 前 后 的 2 部分 代码 的 栈顶 是 同一个,   这 2 部分 代码 是 同一次 函数调用  。

    这就是    协程    和   async await   的 本质区别 之一  。

     

    协程 和 async await  的 另一个 本质区别 是   设计理念   的 不同  ,    async await  是把 异步 变得 看起来 像 同步,   而 协程 是 让 切换 轻量化 、日常化  。

    所以, 协程 要 实现的,  是 恢复 传统 的 编程 模型,   同步 是 同步 ,    异步 是 异步  ,    协程 的 数量 没有 限制,  和 对象 一样,   对象 的 数量 能有多少, 协程 的 数量 就能 有多少,  协程 的 切换 相当于 new 一个对象,   这样 就 能  恢复     传统的 正常的 常规的  编程 模型 范式 架构 思维 思潮    。

     

    这样 就 不需要  异步回调流   为了 节省 线程  就 把  同步 调用 变 异步 ,      再 通过  async await   把 异步 代码 变得 “像” 同步代码   。

    这 很 糟糕    。

     

    也正因为这样 ,     所以 我 说     异步回调流  和  async await    只是一个 过渡,  不可能 长期 霸占 程序界,

    而且 ,   async  await  是 怪物  。          哈哈哈哈哈

     

  • 相关阅读:
    poj 2528 Mayor's posters (线段树+离散化)
    poj 1201 Intervals (差分约束)
    hdu 4109 Instrction Arrangement (差分约束)
    poj 1195 Mobile phones (二维 树状数组)
    poj 2983 Is the Information Reliable? (差分约束)
    树状数组 讲解
    poj 2828 Buy Tickets (线段树)
    hdu 1166 敌兵布阵 (树状数组)
    Ubuntu网络配置
    Button控制窗体变量(开关控制灯的状态)
  • 原文地址:https://www.cnblogs.com/KSongKing/p/11127010.html
Copyright © 2011-2022 走看看