V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
zxCoder
V2EX  ›  问与答

协程(coroutine)到底是啥

  •  
  •   zxCoder · 2021-05-31 21:48:09 +08:00 · 2462 次点击
    这是一个创建于 1270 天前的主题,其中的信息可能已经有所发展或是发生改变。

    想到我头都要炸了,还是没想明白

    中文英文资料都搜了看了一些,就只是知道:

    • 一个线程可以有多个协程
    • 协程调度是用户控制的

    可是这明明很抽象。。。想不明白

    一些语言里的 async 和 await 这种算协程吗,协程到底是个什么概念

    第 1 条附言  ·  2021-06-01 13:58:28 +08:00

    果不其然。。。大家的回复都是那种“懂的都懂”,不懂的实在难以理解

    第 2 条附言  ·  2023-02-25 14:43:15 +08:00

    600多天后,我还是没理解协程,而且在互联网搜索的时候又搜到了这个,悲哀。。。

    14 条回复    2021-07-20 14:50:09 +08:00
    ysc3839
        1
    ysc3839  
       2021-05-31 21:51:13 +08:00   ❤️ 1
    你说的前两点应该描述的是有栈协程,async await 这种是无栈协程。

    我没怎么了解过有栈协程。至于无栈协程,我是理解成“回调函数的语法糖”。
    Jirajine
        2
    Jirajine  
       2021-05-31 21:52:06 +08:00 via Android
    https://os.phil-opp.com/async-await/
    看第一节语言无关的部分
    Nitroethane
        3
    Nitroethane  
       2021-05-31 22:17:51 +08:00   ❤️ 1
    拿 Linux 平台上的 Go 来说明问题。
    Linux 中并没有真正意义上的线程,而被叫做轻量级进程。内核源码注释中进程( process )、线程( thread )和任务( task )经常混用。
    协程就是 Go 的 runtime 实现的**类似于**多线程的机制,协程的调度由 Go 的 runtime 中的协程调度器实现。当一个协程调用了阻塞操作(例如获取锁、读取的 channel 中没有数据等)时,runtime 的协程调度器会将此协程投入等待队列使其睡眠,然后调度其他处于运行队列的协程继续执行。当处于睡眠状态的协程所等待的事件发生后,调度器会唤醒并投入运行队列。
    这个协程调度器是由用户态的程序 Go 自己实现的,跟内核的进程调度器完全没有关系。
    ReferenceE
        4
    ReferenceE  
       2021-05-31 22:39:09 +08:00 via Android
    @ysc3839 async 和 await 是主要是面向 continuation 计算续集的,和协程不是一个东西
    irytu
        5
    irytu  
       2021-05-31 23:04:47 +08:00 via iPhone
    xv6 源码里读一读 swtch.S 文件里的汇编代码,一个简单的 x86 coroutine 实现
    irytu
        6
    irytu  
       2021-05-31 23:05:23 +08:00 via iPhone   ❤️ 1
    ysc3839
        7
    ysc3839  
       2021-05-31 23:09:17 +08:00
    @ReferenceE 我不知道 stackless coroutine 在中文应该怎么翻译,但似乎大家都叫无栈协程,所以我也这么叫了。
    ly841000
        8
    ly841000  
       2021-05-31 23:24:17 +08:00
    用户态调度的非抢占式轻量线程
    zhangbohun
        9
    zhangbohun  
       2021-05-31 23:44:21 +08:00
    不完全严谨的解释,仅供参考:
    线程是操作系统“调度”CPU 算力的最小单位,而所谓“调度”基本就是切换运行时的上下文数据(寄存器里的数据)然后根据 OS 的调度算法换着运行;而协程就是在更上层(比如各种语言的运行时或者工具库的实现)模拟这种暂存上下文数据并且自定义调度算法的轻量级“线程”;而 async 和 await 这个只是语法层面用于标记协程切换和调度的一种方案
    GeruzoniAnsasu
        10
    GeruzoniAnsasu  
       2021-06-01 01:16:45 +08:00   ❤️ 2
    coroutine 是一种无须借助操作系统提供的线程 /进程设施即可实现多任务的机制。

    假设当前有两个 task,执行完全一样的代码 while(1) {print"1"}

    显然由于没有线程,初始执行的那个 task 会一直执行下去,另一个 task 永远没机会执行。


    但如果引入一个调度器和 [打断 /恢复] 的机制,就能够实现 task 间的切换,此时 task 代码类似这样

    while(1) {print"1";yield}

    yield 语句处将执行流跳到这个 task 外,比如调度器;这时候调度器就能将执行流转给另一个 task,另一个 task 从之前 yield 处恢复执行。

    两个 task 不断执行 yield,不断交换执行权,就能让两个 task 的循环都可以同时进行下去,实现并发






    协程需要主动让出执行权,而操作系统的线程借助时间中断可以在当前 task 的任何地方打断并切换,这是协程最典型的特征。不过一般出于写法一致性等理由,会把协程的主动交换点藏在一些不容易发现的地方,比如 await 、yield from 、coroutine.sleep 等,这些语句 /函数其实都是把执行权交到另一个协程( await )或调度器( sleep )上。

    任意一个协程把执行权交给调度器后调度器发现某个协程的 sleep 已超时,它把执行权交回 sleep 的这个协程,从这个协程来看就会好像自己真的 sleep 了一样。执行权交给目标协程后目标协程完全结束并返回,把执行权交给上层 wait 中的协程(完全结束前只可能把执行权交给调度器或第三个协程),从 wait 协程来看就好像自己真的等到目标协程返回才继续一样。




    stackful/stackless 只是实现 [打断 /恢复] 那个恢复机制的做法不同而已
    Rwing
        11
    Rwing  
       2021-06-01 08:31:58 +08:00
    c# 的 async/await 和 js 的 async/await 应该不是一回事吧
    bytesfold
        12
    bytesfold  
       2021-06-01 09:08:06 +08:00
    建议读 《深入了解计算机系统》
    wanguorui123
        13
    wanguorui123  
       2021-06-01 09:35:22 +08:00
    协程就是多线程的 NodeJS 事件队列
    Mogamigawa
        14
    Mogamigawa  
       2021-07-20 14:50:09 +08:00 via iPhone
    接受协程的角色设定就行了,大脑简单点不要太复杂。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5613 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 83ms · UTC 06:32 · PVG 14:32 · LAX 22:32 · JFK 01:32
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.