Kotlin 协程源码学习笔记

Kotlin Coroutine协程源码学习笔记

  • Kotlin Coroutine协程源码学习笔记
    • 什么是协程
    • 协程基本使用方法
    • 协程源码分析

Kotlin Coroutine协程源码学习笔记

学习kotlin有一段时间了,写个博客记录一下。本文基于kotlin 1.3.31来写,如有任何描述不当的地方,请多多指教纠正。

什么是协程

协程是一种轻量级的线程。那和线程有什么不一样?一.线程的调度是由操作系统负责的,它的睡眠、等待、唤醒都是由操作系统控制,开发者无法决定,而协程则可以由开发者决定代码切换的时机;二.线程要放弃CPU使用时间必须要进入阻塞状态,但阻塞的代价是昂贵的,而相比较而言协程可以通过挂起来切换代码,它相比阻塞而言,代价小很多;三.协程运行在线程中,一个线程中可以包含多个协程。

协程基本使用方法

  1. 使用GlobalScope.launch、runBlocking之类的协程构建器构建协程。
fun main(){
	//启动一个协程并且不会阻塞当前线程
	GlobalScope.launch {
		println("start a Coroutine in GlobalScope.launch ")
    }
    //启动一个协程并且阻塞当前线程,直到协程里面所有任务完成
   	//它被设计作为阻塞代码块和挂起风格的库之间的一个桥梁,因此一般用在main
   	//函数或者测试代码中,而不应该在协程中启动。
    runBlocking {
        println("start a Coroutine in runBlocking")
    }
}
  1. 在协程内可使用delay方法或者其他带suspend修饰符的方法表示挂起。带suspend修饰符的方法只能用在协程内,否则会编译报错。

  2. 可以用一个变量引用该协程返回的值(Job),然后在启动该协程的父协程内的合适的位置调用Job的join方法,那么该作业就会在合适的时机调用。

fun main(){
    GlobalScope.launch {
        println("start a Coroutine in GlobalScope.launch ")
        //开启一个运行在线程池中的协程
        val b= async{
            println(" async start")
            println("async end")
        }
        println("GlobalScope.launch running")
        b.join()
        println("end a Coroutine in GlobalScope.launch ")
    }
}
//输出结果:
//	start a Coroutine in GlobalScope.launch 
//	GlobalScope.launch running
//  async start
//	async end 
//	end a Coroutine in GlobalScope.launch
  1. 一个协程中使用多个suspend函数时,函数会按顺序执行。假如需要异步并发执行,使用async。

协程源码分析

协程内部是怎么实现的?使用delay之类的挂起函数,协程是怎么挂起以及恢复的?

接下来从一段代码开始分析:

 // 新建并启动 blocking 协程,运行在 main 线程上,等待所有子协程运行完成后才会结束
fun main() = runBlocking<Unit> {
    println("${Thread.currentThread().name} : runBlocking start")
    // 新建并启动 async 协程,运行在 Dispatchers.Default 的线程池中
    val b = async(Dispatchers.Default) { 
        println("${Thread.currentThread().name} : async start")
        println("${Thread.currentThread().name} : async end")
    }
    b.await()
    println("${Thread.currentThread().name} : runBlocking end")
}

从runBlocking开始,主要的流程如下:

  1. 在创建一个协程Coroutine之前,先为它准备一些必要的元素,EventLoop以及CoroutineContext;
  2. 调用Coroutine.start启动协程;
  3. 调用Coroutine.joinBlocking循环获取要处理的事件。
public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T {
    val currentThread = Thread.currentThread()
    val contextInterceptor = context[ContinuationInterceptor]
    val eventLoop: EventLoop?
    val newContext: CoroutineContext
    if (contextInterceptor == null) {
        // create or use private event loop if no dispatcher is specified
        eventLoop = ThreadLocalEventLoop.eventLoop
        newContext = GlobalScope.newCoroutineContext(context + eventLoop)
    } else {
        // See if context's interceptor is an event loop that we shall use (to support TestContext)
        // or take an existing thread-local event loop if present to avoid blocking it (but don't create one)
        eventLoop = (contextInterceptor as? EventLoop)?.takeIf { it.shouldBeProcessedFromContext() }
            ?: ThreadLocalEventLoop.currentOrNull()
        newContext = GlobalScope.newCoroutineContext(context)
    }
    //创建协程
    val coroutine = BlockingCoroutine<T>(newContext, currentThread, eventLoop)
    //启动协程
    coroutine.start(CoroutineStart.DEFAULT, coroutine, block)
    //循环获取要处理的事件
    return coroutine.joinBlocking()
}

协程的EventLoop用ThreadLocal保存,也就是归每个线程所独有,并且在这里默认值为BlockingEventLoop。EventLoop的作用是管理当前协程的事件,它的一些关键方法都在它的子类EventLoopImplBase里,并且EventLoop也是CoroutineContext中的一个元素;而CoroutineContext用来存放一些当前协程运行时需要用到的东西。

runBlocking生成的协程是BlockingCoroutine,它继承于AbstractCoroutine,BlockingCoroutine只有简单的两个方法,有一个joinBlocking在协程启动后就调用,后续再看看这方法。看来协程主要的一些逻辑都在AbstractCoroutine中了。

private class BlockingCoroutine<T>(
    parentContext: CoroutineContext,
    private val blockedThread: Thread,
    private val eventLoop: EventLoop?
) : AbstractCoroutine<T>(parentContext, true) {
    override val cancelsParent: Boolean
        get() = false // it throws exception to parent instead of cancelling it

    override fun afterCompletionInternal(state: Any?, mode: Int) {
        // wake up blocked thread
        if (Thread.currentThread() != blockedThread)
            LockSupport.unpark(blockedThread)
    }

    @Suppress("UNCHECKED_CAST")
    fun joinBlocking(): T {
    	...
    }
}

协程调用start方法后,通过debug调试跟踪,最后来到了Cancellable.kt文件的startCoroutineCancellable方法里。

internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(receiver: R, completion: Continuation<T>) =
    runSafely(completion) {
        createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellable(Unit)
    }

接下来分析一下这几个方法的作用:

  1. createCoroutineUnintercepted():创建一个非拦截的Continuation。里面最终会调用create()来创建,create()代码是在kotlin编译之后生成的,它会返回一个继承BaseContinuationImpl的Continuation,这个BaseContinuationImpl包含了runBlocking闭包里面要执行的代码。这部分后续再详细分析。

  2. intercepted():方法最终跑到ContinuationImpl里面。使用Context[ContinuationInterceptor].interceptContinuation对第一步返回的Continuation进行拦截,也就是返回一个包装后的Continuation。Coroutine[ContextContinuationInterceptor]就是CoroutineDispatcher,具体实现就是上面创建的BlockingEventLooper。它的interceptContinuation方法直接返回一个DispatchedContinuation对象,DispatchedContinuation既是一个Continuation也是一个Runnable。

  3. resumeCancellable():调用上一步返回的DispatchedContinuation的方法,里面使用一开始创建的BlockingEventLooper.dispatch把DispatchedContinuation作为Runnable传进去。dispatch是BlockingEventLooper父类EventLoopImplBase的一个final方法,在方法里面把DispatchedContinuation放到全局参数_queue队列里。

从上面分析可得知,kotlin编译器把我们写在闭包里面的代码以一个BaseContinuationImpl返回给我们,然后再用一个DispatchedContinuation对BaseContinuationImpl进行了代理,还有就是EventLoopImplBase是负责任务队列的管理。

放进去的任务什么时候取出来执行呢?我们回到runBlocking代码块里。执行完Coroutine.start方法后,紧接着执行joinBlocking。joinBlocking是BlockingCoroutine里面的一个方法,我们来看看它的具体实现。

fun joinBlocking(): T {
        ....
        try {
            ....
            try {
            	//循环取出任务来执行
                while (true) {
                    @Suppress("DEPRECATION")
                    if (Thread.interrupted()) throw InterruptedException().also { cancelCoroutine(it) }
                    val parkNanos = eventLoop?.processNextEvent() ?: Long.MAX_VALUE
                    // note: process next even may loose unpark flag, so check if completed before parking
                    if (isCompleted) break
                   	...
                }
            } finally { // paranoia
                ...
            }
        } finally { // paranoia
            ...
        }
        // now return result
        ...
        return state as T
}

关键就在eventLoop?.processNextEvent() ,我们看看EventLoopImplBase对这方法的具体实现:

    override fun processNextEvent(): Long {
        // unconfined events take priority
        if (processUnconfinedEvent()) return nextTime
        // queue all delayed tasks that are due to be executed
        val delayed = _delayed.value
        if (delayed != null && !delayed.isEmpty) {
            ....
        }
        // then process one event from queue
        dequeue()?.run()
        return nextTime
    }

    private fun dequeue(): Runnable? {
        _queue.loop { queue ->
            when (queue) {
                null -> return null
                is Queue<*> -> {
                    val result = (queue as Queue<Runnable>).removeFirstOrNull()
                    if (result !== Queue.REMOVE_FROZEN) return result as Runnable?
                    _queue.compareAndSet(queue, queue.next())
                }
                else -> when {
                    queue === CLOSED_EMPTY -> return null
                    else -> if (_queue.compareAndSet(queue, null)) return queue as Runnable
                }
            }
        }
    }

从这里可看出,之前放进queue的Runnable,现在又取出来并且调用run()来执行了。DispatchedContinuation并没有实现run方法,具体实现在父类DispatchedTask里。

    public final override fun run() {
        val taskContext = this.taskContext
        var exception: Throwable? = null
        try {
            val delegate = delegate as DispatchedContinuation<T>
            val continuation = delegate.continuation
            val context = continuation.context
            val job = if (resumeMode.isCancellableMode) context[Job] else null
            val state = takeState() // NOTE: Must take state in any case, even if cancelled
            withCoroutineContext(context, delegate.countOrElement) {
                if (job != null && !job.isActive) {
                    val cause = job.getCancellationException()
                    cancelResult(state, cause)
                    continuation.resumeWithStackTrace(cause)
                } else {
                    val exception = getExceptionalResult(state)
                    if (exception != null)
                        continuation.resumeWithStackTrace(exception)
                    else
                        continuation.resume(getSuccessfulResult(state))
                }
            }
        } catch (e: Throwable) {
            // This instead of runCatching to have nicer stacktrace and debug experience
            exception = e
        } finally {
            val result = runCatching { taskContext.afterTask() }
            handleFatalException(exception, result.exceptionOrNull())
        }
    }

这里的continuation就是之前的BaseContinuationImpl,执行resume方法后,就会调用Continuation的resumeWith。BaseContinuationImpl实现了resumeWith,并且是一个final的方法,具体如下:

    public final override fun resumeWith(result: Result<Any?>) {
        // This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume
        var current = this
        var param = result
        while (true) {
            // Invoke "resume" debug probe on every resumed continuation, so that a debugging library infrastructure
            // can precisely track what part of suspended callstack was already resumed
            probeCoroutineResumed(current)
            with(current) {
                val completion = completion!! // fail fast when trying to resume continuation without completion
                val outcome: Result<Any?> =
                    try {
                    	//关键方法
                        val outcome = invokeSuspend(param)
                        if (outcome === COROUTINE_SUSPENDED) return
                        Result.success(outcome)
                    } catch (exception: Throwable) {
                        Result.failure(exception)
                    }
                releaseIntercepted() // this state machine instance is terminating
                if (completion is BaseContinuationImpl) {
                    // unrolling recursion via loop
                    current = completion
                    param = outcome
                } else {
                    // top-level completion reached -- invoke and return
                    completion.resumeWith(outcome)
                    return
                }
            }
        }
    }

BaseContinuationImpl的resumeWith中,关键方法在invokeSuspend,它是BaseContinuationImpl的抽象方法,具体由kotlin编译器实现。invokeSuspend里面执行的就是runBlocking的闭包代码。invokeSuspend还有一个返回值,当返回COROUTINE_SUSPENDED的时候就会跳出循环,那什么时候会返回COROUTINE_SUSPENDED呢?当闭包里面有挂起函数时(如delay),就会返回该值跳出循环。invokeSuspend的实现是一个状态机,就类似于switch case,通过一个状态值跳转来执行闭包里面每一行代码。状态机的实现后续再作分析,接下来继续往下看,假如completion是一个BaseContinuationImpl,则继续递归执行以上代码,否则的话运行completion.resumeWith。这里的completion实际上就是一开始创建的BlockingCoroutine,前面说了BlockingCoroutine只有简单的两个方法,resumeWith是在它父类AbstractCoroutine里面实现。

    public final override fun resumeWith(result: Result<T>) {
        makeCompletingOnce(result.toState(), defaultResumeMode)
    }

这里的resumeWith也是一个final方法,后续的一些调用都进入它的父类JobSupport里面。JobSupport通过一个_state来管理当前协程的状态以及分发恢复挂起的协程。

说到这里,我们对协程的启动运行过程有了个大概的印象,总结一下启动过程:

  1. 先生成一个AbstractCoroutine的子类启动协程。
  2. 在启动过程中获取一个kotlin编译器生成的继承BaseContinuationImpl的类,第一步的AbstractCoroutine作为它的全局的complete参数,接着调用BaseContinuationImpl的resumeWith运行。
  3. BaseContinuationImpl的invokeSuspend方法由kotlin编译器实现,里面通过状态机来跳转执行我们的代码。
  4. 执行完后再调用AbstractCoroutine来修改协程状态。

在前面执行runBlocking的闭包的时候,通过async也创建了一个协程。它的流程和runBlocking差不多,这里只说明一下它的几个不同点:

  1. async生成的是继承AbstractCoroutine的DeferredCoroutine,它的CoroutineDispatcher是DefaultScheduler,它把DispatchedContinuation通过CoroutineScheduler(线程池)放到队列里,再调用createNewWorker创建CoroutineScheduler.Worker(线程)循环从findTask取出要执行的任务。
  2. async会返回一个Deferred,也就是前面的DeferredCoroutine。它可以调用await方法让当前协程等待async执行完之后再执行。

调用了await之后,协程是怎么挂起以及恢复的呢?跟踪一下await的代码,最终来到了JobSupport的awaitSuspend。

    private suspend fun awaitSuspend(): Any? = suspendCoroutineUninterceptedOrReturn { uCont ->
        /*
         * Custom code here, so that parent coroutine that is using await
         * on its child deferred (async) coroutine would throw the exception that this child had
         * thrown and not a JobCancellationException.
         */
        val cont = AwaitContinuation(uCont.intercepted(), this)
        cont.disposeOnCancellation(invokeOnCompletion(ResumeAwaitOnCompletion(this, cont).asHandler))
        cont.getResult()
    }

在awaitSuspend中调用了suspendCoroutineUninterceptedOrReturn,这个方法找不到具体实现,猜测也是编译时生成的代码。里面的参数就是一个实现了BaseContinuationImpl的Continuation,也就是说里面会执行await方法后面的代码。suspendCoroutineUninterceptedOrReturn里面的几个方法就不再展开分析,大概的流程就是用一个AwaitContinuation封装了BaseContinuationImpl,然后再扔到DeferredCoroutine的_state队列里去。

说了这么多,再来梳理一下整个流程:

  1. runBlocking开启协程,把它闭包执行到await的代码对应的BaseContinuationImpl扔到队列里,然后不断循环从队列里取出任务去执行。
  2. async协程启动后调用await时,将await点后面的代码以一个BaseContinuationImpl对象的形式(找不到具体实现,猜测是kotlin编译时自动生成)以及当前的DeferredCoroutine封装到AwaitContinuation中,然后更新到DeferredCoroutine的_state队列中去。
  3. async的闭包执行完后,执行DeferredCoroutine的resumeWith,从_state队列中取出AwaitContinuation,再通过CoroutineDispatcher切换到对应的线程再执行await之后的代码。

在这过程中AbstractCoroutine的作用是管理协程状态以及分发被挂起的协程,BaseContinuationImpl的作用是用状态机切换执行闭包的代码。

参考资料:
https://www.kotlincn.net/docs/reference/coroutines/basics.html
http://johnnyshieh.me/posts/kotlin-coroutine-deep-diving/

你可能感兴趣的:(kotlin)