天天看点

关于Kotlin中“协程一种轻量级线程”的解释

文章目录

      • 前言
      • 问题在哪
      • 协程是什么
      • Kt协程和线程
      • 协程与线程
      • 结论

前言

相信很多人都听过或者看到过这样一种说法“协程是一种轻量级的线程”。以下文档中都有类似的描述:

  1. Kotlin中文网-协程-基础-第一个协程程序
    本质上,协程是轻量级的线程。
  2. 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
}
           

分别看看具体实现

  1. 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
}
           
  1. 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) 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
            }
        }
    
    }
               
    IO和Default最后都会走到CoroutineScheduler的dispatch方法,上述代码为精简后的代码。
  2. 第一行注释就说明了CoroutineScheduler是一个共享线程池。
  3. 熟悉线程池的构造方法和逻辑都能在CoroutineScheduler中看到线程池的影子。
  4. 看看调用逻辑:dispatch --> signalBlockingWork/signalCpuWork --> tryCreateWorker --> createNewWorker–> Worker.start()
    internal inner class Worker private constructor() : Thread() 
               
  5. 扔物线

    Kotlin 的协程用力瞥一眼

    从 Android 开发者的角度去理解它们的关系:
    • 我们所有的代码都是跑在线程中的,而线程是跑在进程中的。
    • 协程没有直接和操作系统关联,但它不是空中楼阁,它也是跑在线程中的,可以是单线程,也可以是多线程。
    • 单线程中的协程总的执行时间并不会比不用协程少。
    • Android 系统上,如果在主线程进行网络请求,会抛出

      NetworkOnMainThreadException

      ,对于在主线程上的协程也不例外,这种场景使用协程还是要切线程的。
    【码上开学】到底什么是「非阻塞式」挂起?协程真的更轻量级吗?

    视频中讲了一个网络 IO 的例子,IO 阻塞更多是反映在「等」这件事情上,它的性能瓶颈是和网络的数据交换,你切多少个线程都没用,该花的时间一点都少不了。

    而这跟协程半毛钱关系没有,切线程解决不了的事情,协程也解决不了。

    协程与线程

    协程我们讲了 3 期,Kotlin 协程和线程是无法脱离开讲的。

    别的语言我不说,在 Kotlin 里,协程就是基于线程来实现的一种更上层的工具 API,类似于 Java 自带的 Executor 系列 API 或者 Android 的 Handler 系列 API。

    只不过呢,协程它不仅提供了方便的 API,在设计思想上是一个基于线程的上层框架,你可以理解为新造了一些概念用来帮助你更好地使用这些 API,仅此而已。

  6. 官方证明轻量级的方式

    运行以下代码:

    import kotlinx.coroutines.*
    
    fun main() = runBlocking {
        repeat(100_000) { // 启动大量的协程
            launch {
                delay(5000L)
                print(".")
            }
        }
    }
               
    可以在这里获取完整代码。

    它启动了 10 万个协程,并且在 5 秒钟后,每个协程都输出一个点。

    现在,尝试使用线程来实现。会发生什么?(很可能你的代码会产生某种内存不足的错误)

    **用协程和线程比这不是欺负人吗?咋不用协程和线程池比呢 **

结论

综上所述,表达一下我自己的结论:Kotlin协程底层是以线程为基础实现的,复用方面类似线程池。所谓的轻量级更多是指使用上面而不是指性能上面,性能上面约等于线程池。