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  是 怪物  。          哈哈哈哈哈

     

  • 相关阅读:
    elasticsearch 事务日志 sync 都干了些什么?
    elasticsearch 事务日志是个啥东西?
    elasticsearch 分片恢复经历了哪些步骤?
    定向爬取网页内容
    文件查询之三:文件和目录的批量操作
    文件查询之二:文件属性查询
    文件查询之一:文件名和文件后缀查询
    记一次SQL联合查询注入工具的编写
    线程间使用socket通信的计算器
    简单的远程加解密文件
  • 原文地址:https://www.cnblogs.com/KSongKing/p/11127010.html
Copyright © 2011-2022 走看看