如果协程仍然使用线程来并行运行代码,为什么它被认为是轻量级的呢?
在我的理解中Kotlin的suspend
函数被编译器转换成状态机,每个分支可以在开发者定义的相同或不同的线程上运行,协程构建器,例如launch{}
,负责这个,CoroutineContext
定义了一个线程。
通过将代码块发送到利用相同线程的线程池来实现并行性
有一个关于100k协程和100k线程的基准测试,其中协程顺利通过,线程抛出异常(可能是OutOfMemory)。
你能帮助我理解这里遗漏了什么吗?是什么让协程并行运行100k代码块,而不像线程那样超过内存限制?
3条答案
按热度按时间x8diyxa71#
从article指向
每个
Thread
都有自己的堆栈,通常为1 MB。64 k是JVM中每个线程允许的最小堆栈空间,而Kotlin中的简单协程只占用几十个字节的堆内存。Coroutine Dispatcher有一个限制,即只能创建一定数量的线程。
例如Dispatchers.IO有64个线程的限制,Dispatchers.Default有处理器上内核数量的限制(2,4,6,8等)Dispatchers.Unconfined无法创建新线程,它运行在其他调度程序先前创建的线程上,证据如下:500个操作的睡眠时间为10 ms,大约需要5s(单线程,因为它不能产生新的)try it yourself。
协程依附于一个线程,一旦到达挂起点,它就离开线程并释放它,让它在等待时拾取另一个协程。这种方式用更少的线程和更少的内存使用,就可以完成那么多的并发工作。
通过回调(如对象
Continuation
)管理协程的挂起和恢复,对象Continuation
在编译时作为最后一个参数添加到用suspend
关键字标记的函数中,该函数与其他对象一样存在于堆中,并负责协程的恢复。因此,RAM中不需要数千MB的空间来保持所有线程的活动。使用CommonPool
最多可以创建典型的60-70个线程,并且可以重用(如果新的协程被创建,它等待直到另一个完成)。iaqfqrcu2#
主要的节省来自于这样一个事实,即通过协作多任务的方式,单个线程可以运行任意数量的协程。当您启动100,000个协程时,它们运行的线程数与CPU核心数一样多,但是当您启动100,000个线程时,JVM将创建同样多的本地线程。请注意,在这两种情况下,并行级别是相等的,并且受CPU核心数的限制。
唯一改变的是日程安排:在经典的情况下,操作系统挂起和恢复线程,将它们分配给CPU内核。使用协程,协程挂起自己(这是它们的协作方面),
Dispatcher
稍后恢复它们,同时运行其他协程。uurity8g3#
**轻量级:**由于支持挂起,您可以在一个线程上运行多个协程,挂起不会阻塞运行协程的线程。挂起可以节省阻塞的内存,同时支持多个并发操作。
**更少的内存泄漏:**使用结构化并发在一个范围内运行操作。