• 协程


    协程

    协程是什么?

    • 协程是可以由程序自行控制挂起、恢复的程序。
    • 协程可以用来实现多任务的协作执行。
    • 协程可以用来解决异步任务控制流的灵活转移。

    协程的作用?

    • 协程可以让异步代码同步化。
    • 协程可以降低异步程序的设计复杂度。
    • 挂起和恢复可以控制执行流程的转移。
    • 异步逻辑可以用同步代码的形式写出。
    • 同步代码比异步代码更加灵活,更容易实现复杂业务。

    线程和协程

    Kotlin协程只是一个“线程框架”?

    • 运行在线程上的框架不一定就是“线程框架”,例如所有框架
    • 支持线程切换的框架也不一定就是“线程框架”,例如OkHttp

    Kotlin协程

    • 官方协程框架(框架级别的支持)

      Job

      调度器

      作用域

    • Kotlin标准库(语言级别的支持)

      协程上下文

      拦截器

      挂起函数


    协程的分类

    按调用栈

    • 有栈协程:每个协程会分配单独的调用栈,类似线程的调用栈。可以在任意函数嵌套中挂起,例如Lua Coroutine
    • 无栈协程:不会分配单独的调用栈,挂起点状态通过闭包或者对象保存。只能在当前函数中挂起,例如Python Generator

    按调用关系

    • 对称协程:调度权可以转移给任意协程,协程之间的关系是对等的。
    • 非对称协程:调度权只能转移给调用自己的协程,协程存在父子关系。

    协程的常见实现

    协程:挂起和恢复

    • Python Generator

    • Go routine

      每个Go Routine都是并发或者并行执行

      无Buffer的channel写时会挂起,直到读取,反之依然

      GoRoutine 可以认为是一种有栈对称协程的实现

    • Lua Coroutine

    • async/await

      很多语言都支持

      可以多层嵌套,但是必须是async function

      async/await是一种无栈非对称的协程实现

      是目前语言支持最广泛的特性

    • ...

    协程基本要素

    挂起函数

    • 挂起函数:以suspend修饰的函数
    • 挂起函数只能在其他挂起函数或者协程中调用
    • 挂起函数调用时包含了协程“挂起”的语义,挂起函数的调用处称为“挂起点”,刮起的时候主调用流程就挂起了。
    • 挂起函数返回时则包含了协程“恢复”的语义,恢复的时候主调用流程就恢复了。

    Continuation

    有栈协程可以把挂起点的状态保存在栈当中,但是无栈协程的话是会保存在闭包当中,而在Kotlin当中是会通过Continuation保存挂起点的状态。

    @SinceKotlin("1.3")
    public interface Continuation<in T> {
        /**
         * The context of the coroutine that corresponds to this continuation.
         */
        public val context: CoroutineContext
    
        /**
         * Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the
         * return value of the last suspension point.
         */
        public fun resumeWith(result: Result<T>)
    }
    
    
    @SinceKotlin("1.3")
    @InlineOnly
    public inline fun <T> Continuation<T>.resume(value: T): Unit =
        resumeWith(Result.success(value))
    
    @SinceKotlin("1.3")
    @InlineOnly
    public inline fun <T> Continuation<T>.resumeWithException(exception: Throwable): Unit =
        resumeWith(Result.failure(exception))
    

    这里的话可以看到,挂起和恢复都是伴随着正常结果的返回和异常结果的返回。

    其实调用一个挂起函数是需要传入一个Continuation的,只是这是编译器已经完成的事情,而只有挂起函数和协程才会拥有一个Continuation。

    挂起函数类型

    比如:suspend() -> Unit、suspend(String) -> String

    • 将回调转写成挂起函数:

      真正的挂起时必须异步调用resume,切换到其他县城resume或者是单线程事件循环异步执行;而如果在suspendCoroutine中直接调用resume也算是没有挂起。

      使用suspendCoroutine获取挂起函数的Continuation,而回调成功的分支使用Continuation.resume(value);而回调失败则使用Continuation.resumeWithException(e)

    协程的创建

    • 首先,协程是一段可执行的程序
    • 协程的创建通常需要一个函数 suspend function
    • 协程的创建也需要一个API。 createCoroutine、startCoroutine

    suspend函数本身执行的时候需要一个Continuation实例在恢复时调用,即此处的参数:completion;返回值Continuation 则是创建出来的协程的载体,receiver;suspend函数会传给该实例作为协程的实际执行体。

    @SinceKotlin("1.3")
    @Suppress("UNCHECKED_CAST")
    public fun <T> (suspend () -> T).createCoroutine(
        completion: Continuation<T>
    ): Continuation<Unit> =
        SafeContinuation(createCoroutineUnintercepted(completion).intercepted(), COROUTINE_SUSPENDED)
    
    @SinceKotlin("1.3")
    @Suppress("UNCHECKED_CAST")
    public fun <T> (suspend () -> T).startCoroutine(
        completion: Continuation<T>
    ) {
        createCoroutineUnintercepted(completion).intercepted().resume(Unit)
    }
    

    协程上下文

    • 协程执行过程中需要携带数据
    • 索引是CoroutineContext.Key
    • 元素是CoroutineContext.Element

    拦截器

    • 拦截器CoroutineIntereptor是一类协程上下文元素
    • 可以对协程上下文所在的协程Continuation进行拦截与篡改。
    @SinceKotlin("1.3")
    public interface ContinuationInterceptor : CoroutineContext.Element {
      public fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T>
      //...
    }
    
    //标准库中的SuspendLambda就是
    @SinceKotlin("1.3")
    // Suspension lambdas inherit from this class
    internal abstract class SuspendLambda(
        public override val arity: Int,
        completion: Continuation<Any?>?
    ) : ContinuationImpl(completion), FunctionBase<Any?>, SuspendFunction {
        constructor(arity: Int) : this(arity, null)
    
        public override fun toString(): String =
            if (completion == null)
                Reflection.renderLambdaToString(this) // this is lambda
            else
                super.toString() // this is continuation
    }
    
    
    //SuspendLambda对应的就是下面suspend包含的部分
    suspend{
      a()
    }.startCoroutine(...)
    
    //而如果suspend里面有调用了挂起函数,在调用的过程中还会用SafeContinuation包装SuspendLambda
    

    Continuaion的执行

    • SafeContinuation的作用就是确保

      • resume只被调用一次

      • 如果在当前线程调用栈上直接调用则不会挂起。

    • 拦截Continuaion

      会用Intercepted先将SuspendLambda包装一次,每次SafeContinuation调用resume的时候会先调用Intercepted返回的Continuation的resume。

      • SafeContinuation仅在挂起点的时候出现
      • 拦截器在每次恢复或者执行协程体的时候调用
      • SuspendLambda是协程函数体

    协程挂起恢复要点

    • 协程体内的代码都是通过Continuation.resumeWith调用
    • 每调用一次lable加1,每一个挂起点对应于一个case分支
    • 挂起函数返回COUROUTINE_SUSPENDED的时候才会挂起

    协程的线程调度

    协程改造的异步程序

  • 相关阅读:
    POJ 1201 Intervals(差分约束 区间约束模版)
    POJ 3660 Cow Contest(求图的传递性)
    POJ 2267 From Dusk till Dawn or: Vladimir the Vampire(最短路变形)
    三角形面积
    欧几里得算法及扩展算法。
    康托展开和排列枚举方式
    并查集 HDU-4424
    poj 2513 并查集,Trie(字典树), 欧拉路径
    hdu 4486
    Pythagorean Triples CodeForces
  • 原文地址:https://www.cnblogs.com/chen-ying/p/13227027.html
Copyright © 2020-2023  润新知