Open bennyhuo opened 4 years ago
看了不少的文章,说了协程是基于语言层面实现的,我是不是可以这样理解,协程最终执行某个任务也是基于内核的线程执行的,只是相比于Java层的线程,两者的调度方式不同,由于协程跟内核线程不是一一对应的关系,所以对于内核线程的创建和切换,没有Java线程那么频繁??
@cooper-zhou 看了不少的文章,说了协程是基于语言层面实现的,我是不是可以这样理解,协程最终执行某个任务也是基于内核的线程执行的,只是相比于Java层的线程,两者的调度方式不同,由于协程跟内核线程不是一一对应的关系,所以对于内核线程的创建和切换,没有Java线程那么频繁??
首先哈,协程可以作为操作系统对 CPU 的直接调度方式,确切的说在线程出现之前,CPU 的调度方式就是协作式的协程了。只不过现在主流操作系统内核都是线程,协程更多见于语言层面的协作调度。
再说你的问题:Kotlin 的协程或者常见的语言里协程实现是要基于内核线程的,也自然不会固定得对应到内核的线程,协程执行的时候挂起恢复也不一定发生线程切换,Kotlin 协程的角度来讲是否发生主要看是否使用了相同的调度器。所以你的理解是没有问题的。
这就是我看了码上开学之后带着疑问的原因了.因为在其他教程里确实详细分析了协程调度过程,看起来并不仅仅是线程框架. 然后我还有一个疑问,kotlin里面的协程调度如果要切线程,基本都使用了线程池,使用池会发生所谓实际的线程切换的消耗吗?比如所谓的上下文和有关线程权限之类的? 问题比较low,但是没搜到我可以确定准确的资料,只能求助了.
@sth4me 这就是我看了码上开学之后带着疑问的原因了.因为在其他教程里确实详细分析了协程调度过程,看起来并不仅仅是线程框架. 然后我还有一个疑问,kotlin里面的协程调度如果要切线程,基本都使用了线程池,使用池会发生所谓实际的线程切换的消耗吗?比如所谓的上下文和有关线程权限之类的? 问题比较low,但是没搜到我可以确定准确的资料,只能求助了.
只要有线程切换,就会有线程切换的开销。所以协程不会给我们消灭线程的开销,而是通过对任务的更轻量级的抽象来减少线程切换的可能。
再来多说说线程、协程和任务的关系。我们过去用线程来承载任务,意味着一个任务就会有一个线程,那么如果任务之间出现交互,例如主线程的UI任务调用 IO 线程的网络请求,UI 任务就会等待 IO任务,并且 UI 对应的线程也需要等待 IO 任务的线程执行,如果不想等(显然也不能等),那就只能回调,这样程序就复杂了;协程呢,作为一种更轻量级的抽象模型(因为只涉及内存,不涉及更多操作系统层面的资源),可以用来承载任务,即便任务执行过程中需要交互,需要等待,任务被暂停(也就是常说的挂起),这时候虽然任务还没有结束,但它因为逻辑上并没有独占线程(只是独占了协程),所以线程可以立即拿出来承载别的任务。
过去是 任务=线程,意味着这二者是绑定到一起的,现在是 任务=协程,协程 N: M 线程(N 通常远大于 M),因而从逻辑上任务对于线程的依赖相当于被协程这个中间层给隔离了,于是任务就变得轻量。
当然,调度,不一定会切线程,因为异步也不一定需要多线程。先搞清楚这一点对于理解协程来讲是很关键的。
哥们大体看了下您的这篇文章,说的很详细,但是我的想法还是不能同意的想法!
以下观点:只是针对 Kotlin
的协程,咋们不考虑其他语言的协程。
handler.post
的例子,我想问下 handler
的本质作用是干嘛的?难道不是做线程通讯的吗?还有你说的那个例子反而让我感觉不是异步,难道不就是一个简单的回调函数吗?Dispatchers.Main
在 Andorid 上的实现难道不是通过 Handler ?我个人认为谁都没说错,没必要去纠结这个写问题?为啥呢?角度不同看到的问题都不同。 我是站在 Java 的角度去看 Kotlin 协程,如果站在 Java 看 Kotlin ,那么 Kotlin 就是一个语法糖。协程就是在对 Thread + Handler 做封装,那么不是线程框架是什么? 你是单纯的把 Kotlin 看出一种语言。 站在不同的角度看到结果是不一样的,你说的也对,我感觉都挺对,只是既然大家在学 Kotlin 那必定是对 Java 有一定了解的,他俩是有密不可分的关系的,我个人认为还是将其当成一种语法糖比较合适。
至于轻量级?还是那句话,你看你在什么角度上去看了,比如官方文档举例,如果我们单纯的把 Kotlin 当成一种语言看待,的确表面上是比较轻量。 那如果我们要追寻原理呢?看看本质呢?2断代码启动的线程数一样多吗?
还是那句话,角度不同,结果不同,都没有错
@makeloveandroid 本来想逐条回复你一下,后来想了想算了,你确实可能需要补充不少的概念。
不过最后那句话说对了一半儿:角度不同,结果不同,这个没问题。不过有没有错这个嘛,有些角度确实比较局限,看到的自然也很容易片面,你说是不是呢?
@hhecoder @makeloveandroid 行吧。
我认为 Kotlin协程就是线程框架,就来切换线程的,他还能干吗?它能创建线程吗?
先定义一下:线程框架。不然这问题不好讨论。 定义好以后再对比下:如果能切线程就是线程框架,那么 RxJava 是线程框架吗?
你说 协程的世界可以没有线程?我想问你 Java 的世界可以没有线程吗?
那是 Java 呀。探讨 Kotlin ,Kotlin 除了 Jvm target,其他的 target 目前的协程确实不会切线程,js 应该一直都不会,Native 现在还不支持。
异步不得2个线程以上还叫异步操作啊?
先搞清楚异步的概念。Asynchrony_(computer_programming)
你举的 handler.post 的例子,我想问下 handler 的本质作用是干嘛的?难道不是做线程通讯的吗?还有你说的那个例子反而让我感觉不是异步,难道不就是一个简单的回调函数吗?
这个不用说了吧。
对于 Dispatchers.Main 在 Andorid 上的实现难道不是通过 Handler ?
想说明什么呢?这个就能说明协程是线程框架?
我个人认为谁都没说错,没必要去纠结这个写问题?为啥呢?角度不同看到的问题都不同。
公司开会扯皮的管用技巧。问题的本质是概念不清,不是角度不同。
我是站在 Java 的角度去看 Kotlin 协程,如果站在 Java 看 Kotlin ,那么 Kotlin 就是一个语法糖。协程就是在对 Thread + Handler 做封装,那么不是线程框架是什么?
那么:我是站在汇编的角度说 C 是一种语法糖我很自豪是吗?
你是单纯的把 Kotlin 看出一种语言。
它不是吗?还是不配?
站在不同的角度看到结果是不一样的,你说的也对,我感觉都挺对。
别和稀泥。
只是既然大家在学 Kotlin 那必定是对 Java 有一定了解的,他俩是有密不可分的关系的
这个当然。
我个人认为还是将其当成一种语法糖比较合适。
但推不出这个结论。
至于轻量级?还是那句话,你看你在什么角度上去看了,比如官方文档举例,如果我们单纯的把 Kotlin 当成一种语言看待,的确表面上是比较轻量。
所以,轻不轻量是客观事实还是您的主观判断呢?
那如果我们要追寻原理呢?看看本质呢?2断代码启动的线程数一样多吗?还是那句话,角度不同,结果不同,都没有错
先把什么是线程框架和什么是异步搞清楚吧。基础不牢,地动山摇哇亲~
@enbandari 事隔几个月,我又从新读了下你的文章。为啥要从新回来读呢?因为我和我的小伙伴都遇到了个坎,死活想不通,突然我想起了你的这篇文章,好像说过这些问题。
事情是这样的:
我尝试学习Flutter,在Flutter中也有async,await这不就和协程一样吗?然后我知道dart是单线程模型,我当时就在想单线程模型是什么啊?然后了解到了下主要内容就是循环读取2个队列的事件执行(有点类似Android的Handler)。
这时候我又猜想Flutter中的 await http.get() 是不是和Kotlin的实现一样,是不是也是最终的 http.get() 操作也是在另外一个线程执行网络请求,然后通过类似Continuation对象,通知并把结果加在对应的Event队列中。
我查询了很多资料,看了官方也和小伙伴们讨论许久,发现所有的文章都说到这里的时候,只给了简单的结论,说是没有开启新的线程。
最后我开始想,IO操作并不是说必须开一个线程去管理。我突然我想到了NIO,如果我这样想哪NIO就不存在了(应该是写Android,总认为IO(读写文件,网络请求)操作就要开线程,思想太片面了)。
最后我总结下我的理解,你帮我看看对吗?
例如:最常见的网络请求。
Koltin对应Andorid的协程:showLoading("加载中")->执行挂起函数Http()->挂起函在子线程网络请求()->请求完毕Continuation通知()->切到主线程DismissDialog()
Dart协程:showLoading("加载中")->执行挂起函数Http()->将读取Http的Response事件加入Event队列->继续循环队列事件()->继续判断Http的Response是否返回(类似NIO的selector)->没有返回Response->继续循环队列事件->此时监测到返回Response->执行挂起点后面的函数。
Dart的整个过程,都没有开启新的线程,也没有什么切线线程操作等。
所以你说协程并不是线程框架,不同的语言实现方式不同。相对于 Koltin对应Andorid 的协程的确耗时性任务必定要切线程,所以让我当时认为协程帮助我们简单切换线程框架。
协程更像一种轻量级的『线程』 我个人认为所有语言的协程更像是类似进程和线程的关系,一个进程中可以跑多个线程,线程切换调用。协程呢?一个线程中可以跑多个协程,协程来回切换调用,大部分实现原理都是通过循环做的。就比如你说的runBlocking官方只是简单的说他能阻塞线程。但是试想下它是如何对应保证runBlocking里面启动所有的协程都执行完毕,才保证线程结束。
关于协程提升性能 就比如NIO说的一个线程可以管理N个流,循环去判断流数据是否到达。
例如:
runBlocking(Dispatchers.IO) {
launch {
println("做事1")
delay(1000)
}
launch {
println("做事2")
delay(2000)
}
launch {
println("做事3")
delay(3000)
}
}
我们想下流程,新建一个线程,启动3个协程,只要有任意1个协程执行了 delay(任意挂起函数) 操作,其他协程都会开始执行代码。这样新创建的线程一直在执行状态。
按照我以前的想法,这种模型的代码对应:
thread {
thread {
println("做事1")
Thread.sleep(1000)
}
thread {
println("做事2")
Thread.sleep(2000)
}
thread {
println("做事3")
Thread.sleep(3000)
}
}
我以前认为 delay 需要等待就需要开线程,其实并不一定的,例如:Handler.postdDelay(xxx),他需要开线程吗?这就是你和我说的让我去了解异步的原因吧!!!
最后我总结,性能先不说提升没,但是我感觉提升了线程的使用率,因为不阻塞么,至于如何达到不阻塞执行挂起点后续的函数,那就要看实现方案了,比如说Dart的循环判断事件队列机制(单线程模型)。
这是我现在的理解,还有理解错误的地方吗?希望能指出,我也描述的不是很清晰,但是感觉有这么点门道了。
@makeloveandroid
最后我总结,性能先不说提升没,但是我感觉提升了线程的使用率,因为不阻塞么,至于如何达到不阻塞执行挂起点后续的函数,那就要看实现方案了,比如说Dart的循环判断事件队列机制(单线程模型)。
嗯,是的。
官方有个 100K 的例子之前被凯哥喷了,其实也是他们没把事儿讲明白。本来协程最重要的使用场景是简化异步代码,非要跟性能扯上关系的话就要考虑整个程序的设计实现了:我用 NIO AIO 就可以提升程序的性能,完全不需要协程,只是 select 或者回调的代码会让程序非常复杂。
协程可以让你更方便的设计出高性能的异步程序。
说到底,如果我们需要用一个线程来对应一个任务,那么这个任务存续期间线程就被独占了,如果任务存在大量 IO,那么线程利用率就很低。这听上去也没什么,IO 不占用 CPU 呀。问题在于线程本身如果没有运行,那么它就被浪费了,它所占用的内存就浪费了。而且线程模型下的任务管理在任务执行切换时实际上就是线程切换,这个也就是线程切换的开销了。如果这里换成协程,同样可以完成任务的承载,内存开销小,而且多数情况下不会引起线程切换,自然也就没有线程切换的开销。这里的各种切换实际上就会有各种异步。
于是正如你所说,协程在承载任务时可以通过提高线程利用率来提升性能。当然,换个角度,是不是之前我们用线程来承载任务的做法本身就是浪费的呢?我们在 NIO 的程序当中已经可以做到提高线程利用率了,一个select 线程就可以监听所有的 IO 事件,再搞几个线程来承载非 IO 的计算任务,从性能的角度来看,没协程什么事儿啊,对吧。问题在于这样的程序很难读懂也很容易写错,Java 的 NIO 的各种文档都说普通人不要自己写 NIO 程序,尽量用 Apache MINA 或者 Netty 这样的框架。但如果用协程封装一下呢?有兴趣可以去看下 Ktor 中的 CIO 实现,其实就是 NIO 的一层封装,API 看上去已经非常亲民了。
说来说去,不管协程带来了哪些好处,本质上就是因为它降低了异步程序的编写复杂度。
另外,单线程模型其实也会有工作线程的。Dart 我没有专门研究过,盲猜会将 await 后面的异步任务调度到工作线程(应该不会太多,一般也就几个线程就足够)上执行,执行完之后再通知主事件循环来触发回调。可以确定的是 Node.js 就是这样做的。
所以,线程框架这个说法其实是不严谨的,如果说协程是异步框架,我看会合适一些。
@enbandari 受教,感谢!!
刚入协程坑不久。如有错误,还望指教哈。
协程处理的就是代码执行流程的切换,所有涉及异步的地方都可以用协程的语法来简化(同步化)。
其实协程有很多场景的作用都跟 Handler post 很接近,这也就是不同手段用于相似的目的而已,本质差异还是很大的。当然,这也不是协程的全部。可以关注下我的 B站账号:bennyhuo 不是算命的,最近也有视频在聊相关的话题~
线程框架这个说法其实是不严谨的,如果说协程是异步框架,我看会合适一些。
这个非常认同。
再进一步理解,是否可以把携程看作 Android Handler 的进阶版本呢?比如,携程中挂起一个函数,就相当于发送了一个 Runnable 到 Handler 中。通过编译器技术,自动插入了 post Runnable 这段代码。同时通过一个普通对象,保存了 post Runnable 的上下文数据(主要是一些局部变量啥的)。
刚入协程坑不久。如有错误,还望指教哈。
— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub, or unsubscribe.
@bennyhuo @hhecoder @makeloveandroid 行吧。
我认为 Kotlin协程就是线程框架,就来切换线程的,他还能干吗?它能创建线程吗?
先定义一下:线程框架。不然这问题不好讨论。 定义好以后再对比下:如果能切线程就是线程框架,那么 RxJava 是线程框架吗?
你说 协程的世界可以没有线程?我想问你 Java 的世界可以没有线程吗?
那是 Java 呀。探讨 Kotlin ,Kotlin 除了 Jvm target,其他的 target 目前的协程确实不会切线程,js 应该一直都不会,Native 现在还不支持。
异步不得2个线程以上还叫异步操作啊?
先搞清楚异步的概念。Asynchrony_(computer_programming)
你举的 handler.post 的例子,我想问下 handler 的本质作用是干嘛的?难道不是做线程通讯的吗?还有你说的那个例子反而让我感觉不是异步,难道不就是一个简单的回调函数吗?
这个不用说了吧。
对于 Dispatchers.Main 在 Andorid 上的实现难道不是通过 Handler ?
想说明什么呢?这个就能说明协程是线程框架?
我个人认为谁都没说错,没必要去纠结这个写问题?为啥呢?角度不同看到的问题都不同。
公司开会扯皮的管用技巧。问题的本质是概念不清,不是角度不同。
我是站在 Java 的角度去看 Kotlin 协程,如果站在 Java 看 Kotlin ,那么 Kotlin 就是一个语法糖。协程就是在对 Thread + Handler 做封装,那么不是线程框架是什么?
那么:我是站在汇编的角度说 C 是一种语法糖我很自豪是吗?
你是单纯的把 Kotlin 看出一种语言。
它不是吗?还是不配?
站在不同的角度看到结果是不一样的,你说的也对,我感觉都挺对。
别和稀泥。
只是既然大家在学 Kotlin 那必定是对 Java 有一定了解的,他俩是有密不可分的关系的
这个当然。
我个人认为还是将其当成一种语法糖比较合适。
但推不出这个结论。
至于轻量级?还是那句话,你看你在什么角度上去看了,比如官方文档举例,如果我们单纯的把 Kotlin 当成一种语言看待,的确表面上是比较轻量。
所以,轻不轻量是客观事实还是您的主观判断呢?
那如果我们要追寻原理呢?看看本质呢?2断代码启动的线程数一样多吗?还是那句话,角度不同,结果不同,都没有错
先把什么是线程框架和什么是异步搞清楚吧。基础不牢,地动山摇哇亲~
@bennyhuo @hhecoder @makeloveandroid 行吧。
我认为 Kotlin协程就是线程框架,就来切换线程的,他还能干吗?它能创建线程吗?
先定义一下:线程框架。不然这问题不好讨论。 定义好以后再对比下:如果能切线程就是线程框架,那么 RxJava 是线程框架吗?
你说 协程的世界可以没有线程?我想问你 Java 的世界可以没有线程吗?
那是 Java 呀。探讨 Kotlin ,Kotlin 除了 Jvm target,其他的 target 目前的协程确实不会切线程,js 应该一直都不会,Native 现在还不支持。
异步不得2个线程以上还叫异步操作啊?
先搞清楚异步的概念。Asynchrony_(computer_programming)
你举的 handler.post 的例子,我想问下 handler 的本质作用是干嘛的?难道不是做线程通讯的吗?还有你说的那个例子反而让我感觉不是异步,难道不就是一个简单的回调函数吗?
这个不用说了吧。
对于 Dispatchers.Main 在 Andorid 上的实现难道不是通过 Handler ?
想说明什么呢?这个就能说明协程是线程框架?
我个人认为谁都没说错,没必要去纠结这个写问题?为啥呢?角度不同看到的问题都不同。
公司开会扯皮的管用技巧。问题的本质是概念不清,不是角度不同。
我是站在 Java 的角度去看 Kotlin 协程,如果站在 Java 看 Kotlin ,那么 Kotlin 就是一个语法糖。协程就是在对 Thread + Handler 做封装,那么不是线程框架是什么?
那么:我是站在汇编的角度说 C 是一种语法糖我很自豪是吗?
你是单纯的把 Kotlin 看出一种语言。
它不是吗?还是不配?
站在不同的角度看到结果是不一样的,你说的也对,我感觉都挺对。
别和稀泥。
只是既然大家在学 Kotlin 那必定是对 Java 有一定了解的,他俩是有密不可分的关系的
这个当然。
我个人认为还是将其当成一种语法糖比较合适。
但推不出这个结论。
至于轻量级?还是那句话,你看你在什么角度上去看了,比如官方文档举例,如果我们单纯的把 Kotlin 当成一种语言看待,的确表面上是比较轻量。
所以,轻不轻量是客观事实还是您的主观判断呢?
那如果我们要追寻原理呢?看看本质呢?2断代码启动的线程数一样多吗?还是那句话,角度不同,结果不同,都没有错
先把什么是线程框架和什么是异步搞清楚吧。基础不牢,地动山摇哇亲~
老哥,我理解的线程框架就是可以对线程进行调度的封装,我的理解是否有误?谢谢
非常赞同“协程不是线程框架”这个论述,就像我们不会说“RxJava是线程框架”一样。我也觉得协程更像是对异步调用的封装,切不切线程都不影响这个核心目的(对异步调用的封装)。
协程 并非 线程框架, 只是协程的 真并发(多核CPU并行) 必须借助线程。如果不需要真并发,也不考虑CPU密集任务阻塞当前线程,那么协程中 就不需要多线程,那时候就完全没有 线程框架 这个概念了。
同一个线程,可以同时开启多个协程,但是同一时刻只有一个协程 可以运行。
在单线程-多协程的 模型中,线程就像个单核CPU一样,同时只能执行一个协程,只不过和 操作系统分配CPU给线程不一样的是,协程的调度是协作式,是由自己主动通知挂起的。(通知给协程调度器)
协程在 CPU密集型操作的时候,会阻塞当前线程!(打破 协程不会阻塞 线程的概念)。 因为CPU密集型运算 无法挂起 且 无法响应中断(cancel), 所以就会阻塞当前线程。久比如 A和B 两个协程在 同一线程在启动,B协程体中delay(3s),但是当A中有CPU密集性任务执行的时候,线程会等待它执行结束,而CPU密集型任务无法挂起,所以等A执行结束后,写成调度器再resume B的时候,就会发现 delay的时间 早已远远超过3s。 所以 为了防止协程阻塞当前线程,加入线程池,将协程分布在不同的线程中。
其实协程就是基于线程池实现的, 启动一个协程就是启动了一个线程,只是说这个线程是线程池来维护,那所谓的挂起和恢复,就是说这个线程的暂停和继续,那么问题来了,他是怎么做到线程的暂停和线程的继续的呢
其实协程就是基于线程池实现的, 启动一个协程就是启动了一个线程,只是说这个线程是线程池来维护,那所谓的挂起和恢复,就是说这个线程的暂停和继续,那么问题来了,他是怎么做到线程的暂停和线程的继续的呢
唉,你再想想吧
@yangzhanlong 其实协程就是基于线程池实现的, 启动一个协程就是启动了一个线程,只是说这个线程是线程池来维护,那所谓的挂起和恢复,就是说这个线程的暂停和继续,那么问题来了,他是怎么做到线程的暂停和线程的继续的呢
启动协程 不是启动一个 线程。 起协程 可以不起线程,可以在当前线程中执行, 而且可以在当前线程(1个线程中) 启动多个协程, 而这多个协程都运行在 同一个 线程中, 是同一个线程中。 协程 感觉更像是 封装好的 task 类, 主要看调度器怎么切换执行(调度了) , 就像 队列 和 栈 都可以用数组来实现, 就看怎么实现存取了。
其实协程就是基于线程池实现的, 启动一个协程就是启动了一个线程,只是说这个线程是线程池来维护,那所谓的挂起和恢复,就是说这个线程的暂停和继续,那么问题来了,他是怎么做到线程的暂停和线程的继续的呢
谢谢大哥让我觉得学习C/C++是有意义的(狗头)。在chatGPT搜索《setjmp.h》,《ucontext.h》第一个是C语言提供的标准库第二个是linux内核提供的,还有一种是直接用汇编。这是协程实现的三种方法:进行寄存器和栈帧的保存和切换。然后实现协程的原理其实就是记录下当前函数执行的寄存器和栈帧,切换到另一个协程的寄存器和栈帧,再配合调度器进行协程间的切换(寄存器和栈帧信息)。从进程到线程再到协程,都是为了以更低的代价更进一步的榨干设备性能,多线程共用进程的一些资源,多协程共用线程的一些资源,所以说协程是轻量级的。协程的优点在于:同步的编程方式,达到异步的性能。(我有测试过多协程跟线程池百万并发的性能,高并发性能上其实没差多少。)
https://www.bennyhuo.com/2019/10/19/coroutine-why-so-called-lightweight-thread/#more
接触新概念,最好的办法就是先整体看个大概,再回过头来细细品味。