问一个协程方面的问题

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

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

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

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

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

各位大佬,救救孩子吧。

12928 次点击
所在节点    Linux
155 条回复
ipwx
2021-12-13 16:16:55 +08:00
@0o0o0o0 其实感觉 async/await 就是协程的一种(个人认为最优美的)表现形式。然这需要大量语法糖支持。

Python 和 JS 的 await 都已经上线了,用起来其实挺爽的。只是 Python 方面的支持库力度还不行。

C++20 的 coroutine 虽然语法糖标准已经发布了,但是根本没有支持的库(差评)。还得等几年。
tpkeeper
2021-12-13 16:18:34 +08:00
缩小了资源调度的粒度
murphytalk
2021-12-13 16:20:48 +08:00
@kingofzihua 异步的 lib 或 API ,传统做法是用回调函数,问题是你要同时协调多个异步调用甚至处理异步调用结果的回调函数里还有其他异步调用的话这些回调函数就会显得凌乱,所以被叫 callback hell 。另外有人对这类被异步调用的回调函数的执行顺序脑子转不过来,协程不增加线程上下文切换的开销,但是能让你以“传统”方式组织逻辑。
不过话说回来,我的一个项目我选的是 vertx 和 kotlin 。vertx 你大概知道所有 API 都是异步的,但 Kotlin 虽有协程但我觉得借助 Vertx 的 Future 和 Promise ,我同样可以把异步逻辑组织得很清爽,我就根本没用 kotlin 的协程。
xx6412223
2021-12-13 16:22:58 +08:00
大多的 IO 密集型任务并不需要独立的线程,而只需要的仅仅是异步
wanguorui123
2021-12-13 16:26:24 +08:00
协程就是用户态的线程,以前线程是操作系统来调度,操作系统的线程调度开销大,而现在协程由用户态上的队列协调器来调度。
可以用少量的线程通过线程里的协调器维护更多的协程,线程少了操作系统内核的调度压力就小了,用户态的协调器是跑在线程上,当遇到异步 IO ,以及协调器提供的伪阻塞这些方法后这个协程就会挂起,这个挂起是轻量级别的,等待下次被唤醒。

有了协程后就可以创建很少的线程,线程间的切换就少了许多,更多的切换是线程内跑的队列协调器,由协调器控制每个协程的状态。
kingofzihua
2021-12-13 16:26:30 +08:00
@ipwx 所以说协程就是一个封装的方法问题? 如果遇到阻塞 IO ,协程就没用了?
目前我只知道网络是非阻塞 IO , 并且这个是操作系统支持的,java 高性能的 网络框架性能也很好的点在于 网络非阻塞 IO ,放到任何语言里都是。
我所说的协程其实根本和性能无关? 我应该吧协程和 callback 或者 event loop(目前还不太清楚是啥) 等 解决并发的方式来比较对吗?
cwcc
2021-12-13 16:28:29 +08:00
@kingofzihua 高性能有不止一种方案实现,协程是其中一种。但协程我个人觉得比较突出的特点就是可以用传统同步代码的逻辑来编写异步才能实现的过程。
wanguorui123
2021-12-13 16:30:29 +08:00
@wanguorui123 JAVA 虽然没有原生的协程,但是 JAVA 有线程池和消息队列,这样也可以用少量的线程维护大量的消息。
ipwx
2021-12-13 16:34:21 +08:00
@kingofzihua

1. 对,协程只对非阻塞 IO 起作用。
2. 但其实,所有上述的并发方法都是针对阻塞 IO 减少开销的。对于 CPU 密集型任务,如果只有 2 个核,同时跑 4 个矩阵计算是没有意义的。
3. 在实践中,通常把 IO 任务和计算任务分开。IO 靠比如 Promise/Future ,event loop ,协程搞。如果需要调用计算任务,就扔到一个固定线程数的线程池上跑,然后协程进入 await 。等计算完成 await 被唤醒,再继续做后面的 IO 。
wanguorui123
2021-12-13 16:34:56 +08:00
@wanguorui123 协程的好处就是 js 中的 asyc await ,实现伪同步代码
retamia
2021-12-13 16:35:59 +08:00
协程就是程序自己去实现内核多线程的保存和切换当前上下文环境。
服务端程序大部分都是 IO 密集型,协程只使用一个 CPU 核就可以跑到很高的处理能力了。

如果你想看实现原理,可以看下 setjmp 和 longjmp 。如果想了解概念和理论,可以参考 LISP 里面的 continuation ,call/cc 概念。
Leviathann
2021-12-13 16:37:04 +08:00
rust 没有 但是语言层面定义好了接口
kotlin 也是 coroutine 是 kotlinx 下面的库不是 kotlin 语言里的东西
语言里有 suspend 关键字
ipwx
2021-12-13 16:37:29 +08:00
通常一个应用程序服务器可以划分为两部分线程组:

1. IO 线程,处理网络 IO 、数据库客户端、等待后台任务、(轻量计算)汇总各种结果。
2. 计算线程,应当是固定数量的线程组成的池,这样一个计算任务丢进来可以排队。

也可以是分离的进程乃至于不同机器处理这些不同的事情。如果是不同进程,可能就会上 zmq 之类的 ipc message queue 。如果是不同机器,那么就会上 rpc 、http api 、redis 或者更重量级的 mq 。
anytk
2021-12-13 16:41:51 +08:00
@kingofzihua
IO 的模型:
以买餐为例,忽略细节
1. 阻塞:经典模式接口。你到餐馆买餐,在餐馆一直等到做好打包,拿了带回来。
2. 非阻塞:电话订餐,每隔小段时间重新打电话问餐馆做好没(其他时间有自由时间处理其他事情),做好了就去拿回来。这电话就是非阻塞调用,但是依然是操作系统调用,会有内核陷入。
3. 异步:操作系统提供接口支持,也就是 event loop 模式。电话订餐,餐做好了餐馆直接电话给你通知好了,你去取餐。

协程用在 IO 密集的场景中,通常都是和 event loop 配合,简化复杂的 callback 序列。
misaka19000
2021-12-13 16:51:02 +08:00
因为线程切换要操作系统做,需要很多操作,从用户层到内核层要进行大量的切换,比较慢;此外线程因为需要保存的变量比较多,涉及到的地方比较多,所以创建的越多耗费的资源越多,导致操作系统不能够创建太多的线程,一般是几千级别。

协程切换需要做的操作比较少,比较快;而且因为协程涉及的资源少,所以可以大量创建,一般可以是十万级。
Orlion
2021-12-13 16:52:17 +08:00
1. 协程解决的问题
java 实现一个支持并发的 http server,最简单的方案可能就是拿到一个连接就创建一个线程来执行,即一个多线程的 server 。这个 server 连接数高了性能就会非常差,因为线程数会很高,线程切换开销比较大。

现在有个协程,因为协程开销比较小,你仍然可以用这个简单的思路去实现,即拿到一个连接就创建一个协程来执行。go 标准库中 http server 就是这么实现的,简单粗暴。在 go 中实际是封装了 epoll(linux 平台),由 runtime 调度协程跑在很少数量的线程上,实际上压根就不是原始的多线程方案了,应该是 epoll 方案,所以对于用户来说能以很简单的方式就能实现高性能并发,这就是协程解决的问题之一。
misaka19000
2021-12-13 16:55:16 +08:00
由于上面所说的操作系统的线程的天然的劣势,所以我们一般避免创建太多的线程,但是在面对 IO 操作的时候尤其是网络 IO ,我们必须要使用大量的线程,所以非阻塞 IO 和多路复用被发明来解决在海量 IO 需要海量线程的问题,使用多路复用+非阻塞 IO 可以保证使用很少的线程就实现大量 IO 读写。

但是多路复用和非阻塞 IO 的问题是什么?问题就是太复杂了,尤其是其中很多的异步操作是反直觉的,导致用起来很麻烦而且代码写起来也是很多异步和回调,代码可维护性很差。协程作为对这些操作的封装,可以让程序员更好的实现海量 IO 的读取操作同时不影响操作系统性能。
misaka19000
2021-12-13 16:55:44 +08:00
可以看我以前写的文章:多路复用、非阻塞、线程与协程

https://www.nosuchfield.com/2019/01/09/Multiplex-and-non-blocking-and-threading-and-coroutine/
kingofzihua
2021-12-13 16:56:00 +08:00
@anytk 稍微懂了,这些博客害人啊,光说比线程轻量,切换开销小,我一直是协程和线程进行对比,以为和线程调度相关,没想到是和 IO 模型相关,不同的处理方式。要说和 callback 相比的话我就能稍微理解点了
xuhaoyangx
2021-12-13 16:56:43 +08:00
协程是可以由程序自行控制挂起恢复的程序。关键词就是挂起恢复。

用协程和不用协程,我理解的优势是

1 、用来多任务协作执行 2 、异步任务控制流灵活转移方便

更浅显点的是 code 层面

1 、异步打码同步话 2 、降低异步程序设计复杂

关于你说的操作系统调度什么的,和协程不是同一层面的东西。

说句不怎么正确的话,协程是线程操作的上层封装?方便你控制

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

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

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

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

© 2021 V2EX