问一个协程方面的问题

2021-12-13 15:04:45 +08:00
 kingofzihua

协程究竟解决了什么问题,都在吹协程,像是 go 、kotlin 都有协程,java 本身没有协程,

还有相比于线程,协程的优势是什么,为啥 java 没有协程,性能框架和 go 框架不相上下,协程这么牛逼为啥 rust 没有内置协程(听说都已经写好了,但是不符合理念,没合并)

操作系统调度的时候是以线程为单位调度的,又不知道协程的存在,即使你用协程了,操作系统还是只知道你是线程啊。

各种博客,都只说了比线程更轻量,占用内存小,切换成本小,但是线程切换是操作系统决定的,系统切换线程,你协程也没用啊。

各位大佬,救救孩子吧。

13019 次点击
所在节点    Linux
155 条回复
kingofzihua
2021-12-14 08:43:58 +08:00
@trcnkq 赞同
kingofzihua
2021-12-14 08:51:34 +08:00
@kekxv 感觉这个例子不太理解,读文件一般是阻塞 IO ,操作系统会直接挂起当前线程,然后切换到其他的线程了,让出 cpu 时间片,这时候当前的线程已经没法继续了,所以 如果你要是大量打开文件,一个协程会对应一个线程,导致线程暴涨,或者是线程池僵死之类的吧。
ai277014717
2021-12-14 09:02:44 +08:00
使用成本更低,因为框架层面 /语言层面来管理线程。对程序员要求更低,
kingofzihua
2021-12-14 09:07:56 +08:00
@letking IO 阻塞中的线程也会被调度吗?线程调度不是应该只调度已就绪的吗?
lux182
2021-12-14 09:17:22 +08:00
@crazywhalecc 多谢
b1iy
2021-12-14 10:02:53 +08:00
作为从 Rxjava 过来的 Androider

我觉得协程精简了线程切换代码量。
Rheinmetal
2021-12-14 10:05:38 +08:00
协程是 cooperative multitaksing 的一种实现形式
尽量减少切换上下文开销
kujio
2021-12-14 10:17:55 +08:00
@kingofzihua 多线程是为了解决并发任务出现的,协程同样也是解决并发任务的,两个作用基本上是一样的,协程本质是一种伪线程,他解决了并发任务的同时比多线程轻量简单,更擅长处理高并发小任务
letking
2021-12-14 11:06:43 +08:00
@kingofzihua 操作系统*不知道*你的线程什么时候会就绪。
你可以在代码里写 sleep(10)或者 sleep(100),但操作系统可不会读你的代码,从而判断出来应该 10s 或 100s 后再来,它只能间隔的执行每一个线程。
dany813
2021-12-14 11:10:30 +08:00
看下来,前端的 async 也有点协程的感觉
liuhan907
2021-12-14 11:32:42 +08:00
@kingofzihua 你先理解一个事情,我们常见的 IO 都有非阻塞模式,不管 callback 也罢还是协程也罢都是为了在等待 IO 完成后继续执行剩下的代码。协程只是给开发者提供一个更简单的方式表达“后续代码”这么个东西而已。
cnoder
2021-12-14 11:43:00 +08:00
就像开水烧和接电话,等开水烧好的时候你人( cpu )是闲的,这时候就可以开协程接电话,等开水烧了再回来取。
到底是两秒钟看一眼开水开没开,还是等开水响了回去处理,这就是协程调度器了
ipwx
2021-12-14 11:45:10 +08:00
@2i2Re2PLMaDnghL 就我个人而言,Actor 模型和 callback / future / 协程不是一个维度的。

actor model 的优势在于 state + mailbox 。每个 actor 上面有个单独的等待队列,即便你在线程池上跑 actor model ,也不会让一个 actor 在多个线程上运行。这使得你可以很安全地编写出来双向交互的 actors ,而不用担心每个 actor 内部的数据存在竞争关系。

线程、协程、callback 、future ,数据流一般是单向的,一个 IO 线程在启动一个后台任务以后就陷入等待,不会处理任何其他事务。而 actor model 每个 actor 都有一个微型 event loop ,完全是全双工的。

你可以很容易用线程池实现 actor model ,避免阻塞调用就能实现完全非阻塞。你也可以在协程上实现 actor model 。或者你也可以在 event loop 上实现 actor model 。总之 actor model 是高于并发的一个抽象模式,核心在于数据封装。
ipwx
2021-12-14 11:48:01 +08:00
@kingofzihua 对既然提到了 actor model ,楼主如果有兴趣可以去看看 actor model ,这个东西的思考方式会让你改变很多对并发的看法。比较成熟的 actor model 的实现大概只有 scala akka 。

actor model 的核心在于,每个 actor 只有一个工作线程。actor 尽量避免阻塞。即使你在 10 个线程的线程池上运行 10000 个 actor ,每个 actor 要同时处理其他 9999 个 actor 发来的消息,也保证只有一个线程在运行一个特定的 actor 。这个是 actor model 最重要的特性。
echoechoin
2021-12-14 11:48:30 +08:00
python 的携程就是 select/poll/epoll 的高级封装
ipwx
2021-12-14 11:48:49 +08:00
…… 不过实现一个基本的 actor framework 也不是那么困难的。。。

再比如我自己在自己的项目里面用 C++ 实现了一个。
lxdlam
2021-12-14 11:59:09 +08:00
“协程”是一个大而空的筐,啥鸡蛋都往里装。Go 的 Goroutine 是 stackful 且在 Go 1.14 之后是基于 signal 的抢占式调度的,Kotlin 是 stackless 但是协作调度的,但是这俩都有人叫做协程。

实际上的“协程”,是一种用户调度抽象。有基本 OS 概念的人都知道,进程( Process )是负责资源隔离的,而真正的运行调度单元是线程( OS Thread )。线程不是免费的,这导致我们如果创建一大堆线程,必定涉及了资源的扩张,而实际上的很多 task 虽然对实时性、执行效率有要求,却不需要这么多资源(比如 IO 任务),我们该如何让这些任务能够更好地执行,而不每次都开一个线程呢?这就是协程的本来目的。

通俗来说,一个语言有没有协程,并不意味着这个语言没有这种抽象调度能力。Lisp 系的 call/cc 和 C 的 setjmp/longjmp 也可以实现某种程度上的协程,甚至更简单的,只要一个语言有能够将一个运行过程封装到一个内存结构上的能力(例如,C++ 有 operator() 的 struct ),基于这个能力写一个 scheduler ,你也发明了一个协程。

协程为何性能高?这也是一个网络迷思:如果你的 task 如上面描述的,是一个低资源占用但是高实时消耗的,那当然是显而易见的性能提升(线程内核态调度延迟一般是比协程用户态调度更慢的),但是这不是银弹,如果你的 task 需要独占资源,那么你的线程调度同样能做到跟协程一样快。

那么,我们该如何认识协程?实际上来说,这是一个异步计算结构的问题:
1. 抢占式调度( preemptive ) vs 协作式调度( cooperative )
2. 有栈结构( stackful ) vs 无栈结构( stackless )
3. 跟系统 Thread 的 mapping 关系:1:1, N:1 和 N:M
等等。这些关键词网络上都有非常优秀的资料,就轮不到我在这里扯淡了。认识协程最关键的部分,实际上还是看语言提供的能力(语言结构、标准库等),以及语言的目标领域。

楼主举得 NIO 的例子实际上跟这个话题相去甚远:NIO 用的是 kernel 提供的 IO Multiplexing 或者 Async IO 能力( epoll/kqueue/io_uring/IOCP ),这个在内核态实现的调度反而还有点类似协程的味道。
a1562619919
2021-12-14 12:03:18 +08:00
@whoosy 进程涉及内核调度,但是线程并不涉及
ipwx
2021-12-14 12:05:31 +08:00
P.S. @kingofzihua 补充一句,actor model 里面每个 actor 其实是个状态机。差不多就是


def __init__(self):
....self.state = 0

def on_message(self, msg):
....if state == 0:
........do something with msg
........self.state = 1
....elif state == 1:
........do something with msg
........self.state = 2

核心就是这个 on_message 处理函数。你是期待框架把新的数据推送给你(或者别的专门读数据的 actor ),而不是在每个 actor 里面都调用 read 。

这个的好处在于,你可以把 IO actor 和业务 actor 切分,业务不用管太多 IO 细节,实现了解耦。单元测试的时候你可以直接对业务 actor 伪造输入进行测试。特别爽。

而且状态机可以回退,这个就可以实现很复杂的东西。
lxdlam
2021-12-14 12:06:04 +08:00
@ipwx Erlang 的 Process 也是很经典的 Actor 实现,在爱立信已经跑了 20 年了

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

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

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

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

© 2021 V2EX