问一个协程方面的问题

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

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

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

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

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

各位大佬,救救孩子吧。

13000 次点击
所在节点    Linux
155 条回复
newmlp
2021-12-13 19:49:09 +08:00
协程就是超级快速在各协程上下文进行切换,类似于 cpu 执行多进程的情况
buffzty
2021-12-13 20:17:31 +08:00
用协程的最主要原因是让你少写代码,减少心智负担. 协程底层就是一个线程池+runtime+任务队列.
java 的 Future,c++ 的 fiber 跟 go 的协程差不多. 不过 go 的协程功能比他两多点. 会自动控制任务的执行时间,协程内部可以让出执行权. 因为 go 的 runtime 帮你写了一大堆代码. 并且开一个新任务只需要 "go" 一个关键字 ,而 java 需要用一个函数包住它.要多写几个字符. go 的协程出名还有一个重要的点就是 语言层支持 chan 通信. 如果你用 java c++都可以实现 go 的 csp 编程, 但是会比 go 多写一些代码 会让你的代码不 java
letking
2021-12-13 20:24:32 +08:00
如上面所说,线程和协程核心的区别就是处理 IO 阻塞时的不同。
操作系统实现的线程调度是一种通用算法,因为操作系统是无法知道你的线程内部究竟在做什么,它只能分时复用隔一段时间来调度一下每个线程,即使某些线程还处于 IO 阻塞状态,操作系统也得把这个线程切换到 CPU 上去执行一下子。
而协程是自己调度的,它不是“切换开销小”而是基本没有所谓的切换开销,因为你自己是完全知道代码何时会进入阻塞、何时会恢复的,发生阻塞时可以安心去执行其他协程,根本无需在中途切换回来看 IO 是否已经就绪。

总的来说多线程就是你交给 CPU 很多个各式各样的任务,CPU 为了保证这些任务都能正常执行,不得不给每个任务分出一个时间片间隔执行,保证所有任务都在运行。多协程是你把多个任务的 DAG 图都画好了交给 CPU ,CPU 只需要按照你的图按部就班地线性的执行即可。
shew2356
2021-12-13 21:04:31 +08:00
Java 是支持协程的~
wyx119911
2021-12-13 21:11:20 +08:00
通过没有原生协程语言的协程化改造,可以更好地理解协程的意义。
如果是重 io 轻计算的场景,使用 io 多路复用进行事件回调( NIO ,epllo )优化并发量比粗暴开线程更省资源,效果更佳。但是这意味着极大提高 io 编程时的工作量和心智负担。
一个 while(true) {
read();
}
wyx119911
2021-12-13 21:18:22 +08:00
通过没有原生协程语言的协程化改造,可以更好地理解协程的意义。
如果是重 io 轻计算的场景,使用 io 多路复用进行事件回调( NIO ,epoll )优化并发量比粗暴开线程更省资源,效果更佳。但是这意味着极大提高 io 编程时的工作量和心智负担。
所以一个 while(true) { read(); } 要改造就要编写维护大量 epoll 事件。
c++在没有原生协程支持的情况下,引入 libco 这样的库,可以不改动代码的情况下异步协程化。做法是 hook 这个同步 read 函数,在里面添加、回调 epoll 。使用方无感知,降低了开发复杂度,这就是协程的优点。
qingtengmuniao
2021-12-13 21:47:47 +08:00
协程,本质上是用户态线程。其好处在于占用资源更少,上下文切换代价更小。对于计算密集型应用来说,可以轻松开到十万级以上并发。
线程、协程本质是什么?是一个逻辑上的顺序执行流,这种模型能极大降低编程难度。因此,尽管可以通过在线程内做异步编程、IO 多路复用等等策略来充分利用 CPU ,但他们会让用户代码变得复杂而难读。
总结来说,协程使得用户可以编写同步代码,来充分利用 CPU 。而将协程上下文保存、协程调度等细节交给协程库去处理。
BigDogWang
2021-12-13 22:04:58 +08:00
楼上说的很清楚了,补充一点,IO 接口不一定会一定阻塞。系统 IO 接口有一种是非阻塞 IO 接口,并不会挂起当前线程。甚至于我猜测协程就是基于非阻塞 IO 接口和消息队列的
BigDogWang
2021-12-13 22:06:29 +08:00
@BigDogWang 你也可以不用协程自己有非阻塞 IO 和消息队列搞一套类似的。问题的关键在于没有语言层面的支持,你会写出一堆回调的
NeroKamin
2021-12-13 22:57:31 +08:00
协程最大的作用还是降低编码心智负担
wyx119911
2021-12-13 23:30:23 +08:00
@qingtengmuniao “对于计算密集型应用来说,可以轻松开到十万级以上并发”这句是不是打错了?
FrankAdler
2021-12-14 00:16:05 +08:00
看了上面的很多回答,真不喜欢程序员举例子
kisick
2021-12-14 00:56:19 +08:00
@qingtengmuniao 计算密集型的场景,为什么要开协程,看了上面的回答,协程不是用于非阻塞 IO 时吗?
limbo0
2021-12-14 01:07:43 +08:00
看了楼里, 觉得自己理解太肤浅了
1423
2021-12-14 01:27:05 +08:00
感觉全部人都在空谈。。没有实际经验

就我而言,协程颠覆了网络编程
在协程出现前,网络编程库( C/C++)用 epoll 那一套,库对外提供的接口往往是回调接口;就是当某事发生时,库会调用你指定的函数来处理事件。

毫无疑问 epoll 的使用和回调的管理是比较难处理的,这也是网络编程成为一个“技能点”的原因

协程广泛使用后,C/C++ 出现了基于协程的网络库,一般 hook 了 read write 等系统调用,使得调用者不再需要关心控制流切换,更不用使用异步回调的方式进行网络编程。

但 C/C++ 库无法彻底避免用户对会造成阻塞的网络 api 的调用。一旦发生就是灾难。

而 golang 的出现解决了这一问题,golang 是语言级别的协程,彻底的解决了“网络编程是困难的”这一问题,并且几乎是网络编程的最佳实践。

golang 对网络编程的贡献使得无数人可能引以为傲的 C/C++ 网络编程技能变得不再重要,因为用 go 实现工业级网络协议处理的门槛大大降低了。
1423
2021-12-14 01:32:15 +08:00
协程是很早就有了的技术,之所以最近几年被大众瞩目,就是因为 golang 用了协程。
协程对控制流的管理不是新鲜事物,C/C++ 等语言实现的协程机制也不是新鲜事物。
协程被 go 广泛引入到公司级的网络程序中,才是新鲜事物。

一家之言,仅供参考,欢迎批评
ysc3839
2021-12-14 01:41:10 +08:00
我个人不是很懂协程,只是稍微看过一点无栈协程的资料,感觉上有栈协程和无栈协程还是有些区别的,建议学习时分开研究。
个人觉得无栈协程更像是回调函数的语法糖,不必须要调度器 (但是要配合调度器用也是可以的),可以很容易跟一些使用回调函数的场景对接。个人建议看看 C++20 的 coroutine 和 JavaScript 的 async await 。Python 的 async function 比较特殊,说是无栈协程,但是捆绑了调度器,不能像回调函数那样使用。
至于有栈协程我就不太了解了,感觉上比无栈协程复杂。
2i2Re2PLMaDnghL
2021-12-14 01:41:17 +08:00
实际上就是『在线程内执行其他的』,只是我们将其中一种方法取了个名字叫『协程』
用 stackful 的形式去描述在一个线程内的多项计算,在文法和语义界面上就呈现出一种『活动的栈的切换』(实际上是否发生栈切换视具体实现而定)。而这种活动的栈的切换看上去又和线程切换类似,所以就认为它是一种更轻量的线程。

话说回来,这么多楼竟然没有一个人谈到 Actor 模型的。

也只有一个人谈到 call/cc ;实际上,你会发现 lisp 语言的大部分方言根本不用进行任何改变就支持协程,你大可以自己实现,Wikipedia 的列表里将其称为 trivial 的。
当然,Coq 里你甚至可以自行实现一个 call/cc ,但它的执行结构似乎不能正确地设计出协程()
ysc3839
2021-12-14 01:52:03 +08:00
@1423 我觉得要分开来看。有栈协程好像上世纪 90 年代就已经出现了,最近才随着 Golang 热门起来。
另一边无栈协程或者说 async function/generator 则是近十年随着 C#, JavaScript, C++ 逐渐发展起来。
zhang2e
2021-12-14 07:36:49 +08:00
说说我了解的 Kotlin 在 Android 中实现的协程吧( Kotlin Jvm 中的协程的实现是另一种),本质上目的就是帮你切换线程,你不直接操作线程,你也无需了解线程的知识,但你可以说他就是线程。

那为什么要发明这种东西呢,做过 Android 开发都知道,线程切换是很频繁的事情,需要在子线程中异步处理耗时任务,然后还要切换回主线程中处理 UI 操作。

Android 原生给的方案是 Handler 和 Looper ,这样写就是不可避免遇到回调地狱和线程管理中不恰当创建切换线程带来的性能损耗问题。而协程把上面的脏活累活都给你干了。

之前的 RxJava ,也是解决这个问题,不过协程可以像写同步一样写异步,更符合直觉,更可读。

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

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

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

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

© 2021 V2EX