协程跟 cpu 有关系吗?

2020-12-03 20:56:01 +08:00
 vevlins

我觉得啥关系也没有。

通过 cps 实现 call/cc->通过 call/cc 实现协程。以我的理解,cps 就是编译器层面的自动 callback,纯粹是语言层面的东西,只要能够做 callback->也就是能够 JMP->也就是移动程序指针->也就是图灵机的最基本要求,就可以实现协程,整个实现过程跟 cpu 没什么关系。

在 C/C++语言中有什么区别吗?我是按照 JS 的知识分析的。

3932 次点击
所在节点    程序员
31 条回复
Mohanson
2020-12-03 21:06:59 +08:00
就拿 riscv 来说, CPU 在操作 RAS(return address stack)的时候需要做特别的处理以支持协程.

> When two different link registers (x1 and x5) are given as rs1 and rd, then the RAS
is both popped and pushed to support coroutines.

另外, 协程底层是线程, 线程的实现是需要 CPU 提供底层能力支持的(需要能实现自旋锁)

"我觉得啥关系也没有。" 我不要你觉得, 我要我觉得~
DoctorCat
2020-12-03 21:14:45 +08:00
cps 是啥?
emeab
2020-12-03 21:41:43 +08:00
确实没啥关系。
lewis89
2020-12-03 22:06:17 +08:00
协程是协作式的,例如我一个协程要调用 IO 了,我就通知 golang 的 IO 管控线程,我把 IO 调用 写进缓冲区 交给管控线程,然后你这个协程就放弃线程的 CPU 时间片,让其它的协程去跑起来,然后管控的线程就帮你把 IO 的请求 写进 fd-set 然后交给 epoll 调用 ,epoll 返回回来 发现你上次写的 IO 返回了,管控的线程就让你这个协程加进调度队列,等下次别的线程空出来的时候,你这个协程就可以继续跑起来干活。

传统的 Java 线程池就是 线程我要写 IO 了,然后调 IO pending 住,这个线程就去内核睡大觉了,如果线程 IO 多了,大家都得去内核睡大觉,内核睡大觉 有好几个调度队列 ,前面的人醒来干完自己的时间片才能轮到你,在内核睡大觉 还有一个麻烦就是 ring3 切换到 ring0 使用 int3 特权指令耗时 而且频繁切换 内核到应用态的上下文 很耗时,协程的话 在应用态只要把 几个寄存器跟 PC 寄存器 换到内存 然后把栈空间维持住,下次就可以实现协作式的调度,缺点是没有时间片(时间片只有内核中断向量注册后 才能响应硬件时间中断 类似的嵌入式芯片还有 watchdog 以免代码跑飞 ),有的协程跑了老半天 可能没跑到 协程退出占用的地方就把别的协程给饿死了。
linux40
2020-12-03 22:11:21 +08:00
我觉得楼主的理解没啥大问题,归结到实现上就是利用了 continuation 的概念。
misaka19000
2020-12-03 22:14:00 +08:00
no1xsyzy
2020-12-03 22:24:35 +08:00
@Mohanson 这似乎是 CPU 实现了协程?还是实现了对协程的分支预测?
no1xsyzy
2020-12-03 22:30:58 +08:00
@lewis89 @misaka19000 为什么在解释协程是什么?喊得出 call/cc 的人需要你们教基础概念?
codehz
2020-12-03 22:31:34 +08:00
你这种论证方法似乎可以套在任何一个实体上啊。。。
因为你这定义就不清晰,别放一堆高级词汇,就解释下什么叫和 cpu 有关,什么叫和 cpu 无关。。。
lewis89
2020-12-03 22:31:50 +08:00
@no1xsyzy #7 怎么会有分支预测,协程 协程 就是协作式的调度方式,也就是说 一个协程 霸占着线程 完全可以不退出,因为没有时钟中断,完全可能协程占着那个线程不放,协程要退出来只能主动退出调度,例如你写了个协程

for i=0; i++ ;i <1000
{
干活;

看一看是不是干了好久要不要先退出去让别的协程跑一跑?

function();
调了个函数 被 golang hook 住了 要不要先退出去 让别的协程跑一跑?

}



线程

for i=0; i++ ;i <1000
{
干活; <-- 去你娘的 时间片到了 回内核睡大觉吧。
function();
}
no1xsyzy
2020-12-03 22:33:00 +08:00
kotlin 的协程的底层 API 的命名甚至都明示了其实就是一个一次性的 continuation
lewis89
2020-12-03 22:33:35 +08:00
@no1xsyzy #8 不需要基础概念吗?

理论上讲 你只要知道 RSP RPB 跟 PC 三个 X86 指针 在汇编层面上 实现个协程就是分分钟的事情
no1xsyzy
2020-12-03 22:35:17 +08:00
@lewis89 https://stackoverflow.com/q/55926030/6202760
看问题的 comment 似乎说是分支预测在尝试预测协程会让出时间片还是 return
lewis89
2020-12-03 22:35:55 +08:00
@no1xsyzy #11 回去读 CSAPP 吧, 或者看看 X86 汇编手册,RSP RBP 加 PC 指针 然后使用 mprotect 注册内存访问保护 实现中断信号量访问,唯一的确定就是没有时间相应, 用这个几个东西 完全就可以实现简易版的协程
Mohanson
2020-12-03 22:36:28 +08:00
@no1xsyzy 支持对协程进行返回地址预测(Return-address prediction), 理解为分支预测也没有问题.
no1xsyzy
2020-12-03 22:37:09 +08:00
@lewis89 你知道 CPS 的话可以在没有 call stack 的情况下实现协程……
lewis89
2020-12-03 22:39:38 +08:00
@no1xsyzy #16 还要依赖特定指令集? 传统的协程 确实就是 保存一下 stack 栈帧 切换一下 PC 寄存器就完事了,我确实不了解除此之外的协程方式
misaka19000
2020-12-03 22:46:00 +08:00
@no1xsyzy #8 抱歉没仔细看题主的问题

协程当然和 CPU 没有关系,只是一个逻辑上的概念,代表了对当前的状态以及对状态处理的一种逻辑的抽象。所以你说的对,一个 jmp 也就是图灵机移动指针即可实现协程。

但是,一个 jmp 也可以实现线程啊,所以如果你在讨论协程的话,一般都会与线程放在一起讨论对比的。

按照图灵机的逻辑,线程和协程其实都只是一种逻辑流以及运行状态的抽象。
misaka19000
2020-12-03 22:48:01 +08:00
协程和线程的根本区别在于一个是抢占式的,一个不是抢占式的

至于一个工作在内核态一个工作在用户态则是由于操作系统的特性决定的,因为用户态程序无法实现进程 schedule
Wincer
2020-12-03 23:03:13 +08:00
楼上说的栈切换和 call/cc 其实并不是两样东西。在 Scheme 中,continuation 本身就可以通过操作栈空间的方法来实现,但这仅仅是一种实现方式。如果你要问不同语言之间是否有区别,那当然也是有的。那是不是所有语言都能用 call/cc 来实现协程呢,理论上可行——只需要用他们写一个 lisp 的解释器就行了。

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/731898

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX