文章目录
-
-
- 前言
- 问题在哪
- 协程是什么
- 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 {
@JvmStatic
public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
@JvmStatic
public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
@JvmStatic
public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
@JvmStatic
public val IO: CoroutineDispatcher = DefaultScheduler.IO
}
分别看看具体实现
- Main
internal class AndroidDispatcherFactory : MainDispatcherFactory {
override fun createDispatcher(allFactories: List<MainDispatcherFactory>) =
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: Int
get() = Int.MAX_VALUE / 2
}
- IO和Default
IO和Default最后都会走到CoroutineScheduler的dispatch方法,上述代码为精简后的代码。/** * 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) return signalCpuWork() } 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 operational if (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 scheduler if (isTerminated) return -1 val state = controlState.value val created = createdWorkers(state) val blocking = blockingTasks(state) val cpuWorkers = (created - blocking).coerceAtLeast(0) // Double check for overprovision if (cpuWorkers >= corePoolSize) return 0 if (created >= maxPoolSize) return 0 // start & register new worker, commit index only after successful creation val newIndex = createdWorkers + 1 require(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] = worker require(newIndex == incrementCreatedWorkers()) worker.start() return cpuWorkers + 1 } } }
- 第一行注释就说明了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协程底层是以线程为基础实现的,复用方面类似线程池。所谓的轻量级更多是指使用上面而不是指性能上面,性能上面约等于线程池。