关于Kotlin中“协程一种轻量级线程”的解释
文章目录
- 前言
- 问题在哪
- 协程是什么
- Kt协程和线程
- 协程与线程
- 结论
前言
相信很多人都听过或者看到过这样一种说法“协程是一种轻量级的线程”。以下文档中都有类似的描述:
-
Kotlin中文网-协程-基础-第一个协程程序
本质上,协程是轻量级的线程。
-
Kotlin英文官网对应位置
Essentially, coroutines are light-weight threads.
显然,翻译很准确
问题在哪
相信很多人了解过Kt协程都看过上线官方的这个描述。
因而很多人觉得协程比线程牛逼,因为是”轻量级“。所以有人觉得协程是另一种方式实现的类似线程的功能,所以产生了以下对话:
A:Kotlin协程和线程有啥区别
B:协程是对线程操作的封装,通过状态机和回调实现以同步样式代码书写异步逻辑,消除回调低于,更便于理解,巴拉巴拉。。。
A:内存消耗上有啥区别?
B:内存消耗上如果线程和协程比的话协程更占优势,因为协程底层实现是类似线程池,有线程复用,所以更省内存,如果是线程池和协程比较,实际上并无太大区别。
A:这和我的理解不太一样呢,我的理解是协程是通过编译器实现的,所以比线程更节省内存,比如我开100w个线程和100w个协程,线程会oom,协程不会
协程是什么
广义上的协程是一个**概念(协程是一种非抢占式或者说协作式的计算机程序并发调度)**而不是一个具体的框架。
Kt中的协程是Kt对协程概念的一种具体实现。
Kt协程和线程
Kt协程被官方描述为轻量级线程。
那它跟线程到底是什么关系呢,是包含封装关系还是功能类似的两种不同的实现方式呢?
康康源码?show me the fucking code!!
协程的切换线程必然要使用到Dispatchers中的几个变量
public actual object Dispatchers {@JvmStaticpublic actual val Default: CoroutineDispatcher = createDefaultDispatcher()@JvmStaticpublic actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher@JvmStaticpublic actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined@JvmStaticpublic val IO: CoroutineDispatcher = DefaultScheduler.IO
}
分别看看具体实现
- Main
internal class AndroidDispatcherFactory : MainDispatcherFactory {override fun createDispatcher(allFactories: List) =HandlerContext(Looper.getMainLooper().asHandler(async = true), "Main")override fun hintOnError(): String? = "For tests Dispatchers.setMain from kotlinx-coroutines-test module can be used"override val loadPriority: Intget() = Int.MAX_VALUE / 2
}
-
IO和Default
/*** Coroutine scheduler (pool of shared threads) which primary target is to distribute dispatched coroutines* over worker threads, including both CPU-intensive and blocking tasks, is the most efficient manner...............**/@Suppress("NOTHING_TO_INLINE") internal class CoroutineScheduler(@JvmField val corePoolSize: Int,@JvmField val maxPoolSize: Int,@JvmField val idleWorkerKeepAliveNs: Long = IDLE_WORKER_KEEP_ALIVE_NS,@JvmField val schedulerName: String = DEFAULT_SCHEDULER_NAME ) : Executor, Closeable {fun dispatch(block: Runnable, taskContext: TaskContext = NonBlockingContext, tailDispatch: Boolean = false) {val task = createTask(block, taskContext)if (task.mode == TASK_NON_BLOCKING) {if (skipUnpark) returnsignalCpuWork()} else {signalBlockingWork(skipUnpark = skipUnpark)}}private fun signalBlockingWork(skipUnpark: Boolean) {if (tryCreateWorker(stateSnapshot)) return}internal fun signalCpuWork() {if (tryCreateWorker()) return}private fun tryCreateWorker(state: Long = controlState.value): Boolean {val created = createdWorkers(state)val blocking = blockingTasks(state)val cpuWorkers = (created - blocking).coerceAtLeast(0)/** We check how many threads are there to handle non-blocking work,* and create one more if we have not enough of them.*/if (cpuWorkers < corePoolSize) {val newCpuWorkers = createNewWorker()// If we've created the first cpu worker and corePoolSize > 1 then create// one more (second) cpu worker, so that stealing between them is operationalif (newCpuWorkers == 1 && corePoolSize > 1) createNewWorker()if (newCpuWorkers > 0) return true}return false}/** Returns the number of CPU workers after this function (including new worker) or* 0 if no worker was created.*/private fun createNewWorker(): Int {synchronized(workers) {// Make sure we're not trying to resurrect terminated schedulerif (isTerminated) return -1val state = controlState.valueval created = createdWorkers(state)val blocking = blockingTasks(state)val cpuWorkers = (created - blocking).coerceAtLeast(0)// Double check for overprovisionif (cpuWorkers >= corePoolSize) return 0if (created >= maxPoolSize) return 0// start & register new worker, commit index only after successful creationval newIndex = createdWorkers + 1require(newIndex > 0 && workers[newIndex] == null)/** 1) Claim the slot (under a lock) by the newly created worker* 2) Make it observable by increment created workers count* 3) Only then start the worker, otherwise it may miss its own creation*/val worker = Worker(newIndex)workers[newIndex] = workerrequire(newIndex == incrementCreatedWorkers())worker.start()return cpuWorkers + 1}}}IO和Default最后都会走到CoroutineScheduler的dispatch方法,上述代码为精简后的代码。
-
第一行注释就说明了CoroutineScheduler是一个共享线程池。
-
熟悉线程池的构造方法和逻辑都能在CoroutineScheduler中看到线程池的影子。
-
看看调用逻辑:dispatch --> signalBlockingWork/signalCpuWork --> tryCreateWorker --> createNewWorker–> Worker.start()
internal inner class Worker private constructor() : Thread() -
扔物线
Kotlin 的协程用力瞥一眼
从 Android 开发者的角度去理解它们的关系:
- 我们所有的代码都是跑在线程中的,而线程是跑在进程中的。
- 协程没有直接和操作系统关联,但它不是空中楼阁,它也是跑在线程中的,可以是单线程,也可以是多线程。
- 单线程中的协程总的执行时间并不会比不用协程少。
- Android 系统上,如果在主线程进行网络请求,会抛出
NetworkOnMainThreadException,对于在主线程上的协程也不例外,这种场景使用协程还是要切线程的。
【码上开学】到底什么是「非阻塞式」挂起?协程真的更轻量级吗?
视频中讲了一个网络 IO 的例子,IO 阻塞更多是反映在「等」这件事情上,它的性能瓶颈是和网络的数据交换,你切多少个线程都没用,该花的时间一点都少不了。
而这跟协程半毛钱关系没有,切线程解决不了的事情,协程也解决不了。
协程与线程
协程我们讲了 3 期,Kotlin 协程和线程是无法脱离开讲的。
别的语言我不说,在 Kotlin 里,协程就是基于线程来实现的一种更上层的工具 API,类似于 Java 自带的 Executor 系列 API 或者 Android 的 Handler 系列 API。
只不过呢,协程它不仅提供了方便的 API,在设计思想上是一个基于线程的上层框架,你可以理解为新造了一些概念用来帮助你更好地使用这些 API,仅此而已。
-
官方证明轻量级的方式
运行以下代码:
import kotlinx.coroutines.*fun main() = runBlocking {repeat(100_000) { // 启动大量的协程launch {delay(5000L)print(".")}} }可以在这里获取完整代码。
它启动了 10 万个协程,并且在 5 秒钟后,每个协程都输出一个点。
现在,尝试使用线程来实现。会发生什么?(很可能你的代码会产生某种内存不足的错误)
**用协程和线程比这不是欺负人吗?咋不用协程和线程池比呢 **
结论
综上所述,表达一下我自己的结论:Kotlin协程底层是以线程为基础实现的,复用方面类似线程池。所谓的轻量级更多是指使用上面而不是指性能上面,性能上面约等于线程池。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
