协程-执行过程

一、前言

在前面已经了解了协程的使用方法,为了能够更深入的了解协程的设计原理,那么我们来探究以下底层的实现原理。

二、执行过程分析

我们了解到,协程的创建和执行需要在CoroutineScope中,下面我们借助源码来分析CoroutineScope的执行过程。

2.1 创建CoroutineScope对象

    val scope = CoroutineScope(Job()+Dispatchers.IO)

CoroutineScope的创建很简单,传入一个CoroutineContext。

下面我们看一下CoroutineScope的代码:

/**
 * Creates a [CoroutineScope] that wraps the given coroutine [context].
 *
 * If the given [context] does not contain a [Job] element, then a default `Job()` is created.
 * This way, cancellation or failure of any child coroutine in this scope cancels all the other children,
 * just like inside [coroutineScope] block.
 */
public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
    ContextScope(if (context[Job] != null) context else context + Job())

CoroutineScope返回一个根据我们传入的CoroutineContext进行包装,如果传入的context没有Job,那么会使用默认的Job(),CoroutineScope()函数返回ContextScope,ContextScope的代码比较简单,实现CoroutineScope接口,重写coroutineContext属性,并赋值。

internal class ContextScope(context: CoroutineContext) : CoroutineScope {
    override val coroutineContext: CoroutineContext = context
    // CoroutineScope is used intentionally for user-friendly representation
    override fun toString(): String = "CoroutineScope(coroutineContext=$coroutineContext)"
}

ContextScope的创建过程中,我们看到一个特殊的操作操作符“+”,这个对应的是CoroutineContext中的plus方法

public interface CoroutineContext {

    public operator fun plus(context: CoroutineContext): CoroutineContext =
        if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
            context.fold(this) { acc, element ->
                val removed = acc.minusKey(element.key)
                if (removed === EmptyCoroutineContext) element else {
                    // make sure interceptor is always last in the context (and thus is fast to get when present)
                    val interceptor = removed[ContinuationInterceptor]
                    if (interceptor == null) CombinedContext(removed, element) else {
                        val left = removed.minusKey(ContinuationInterceptor)
                        if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
                            CombinedContext(CombinedContext(left, element), interceptor)
                    }
                }
            }
    
}

这里关于plus的具体实现可以参考源码中各个CoroutineContext子类的实现,下面用一张图来表示plus()函数最终的效果:
image

这里有个EmptyCoroutineContext对象,我们也需要了解一下它的实现:

public object EmptyCoroutineContext : CoroutineContext, Serializable {
    private const val serialVersionUID: Long = 0
    private fun readResolve(): Any = EmptyCoroutineContext

    public override fun <E : Element> get(key: Key<E>): E? = null
    public override fun <R> fold(initial: R, operation: (R, Element) -> R): R = initial
    public override fun plus(context: CoroutineContext): CoroutineContext = context
    public override fun minusKey(key: Key<*>): CoroutineContext = this
    public override fun hashCode(): Int = 0
    public override fun toString(): String = "EmptyCoroutineContext"
}

这里的EmptyCoroutineContext定义为了一个单例,实现CoroutineContext中相关的方法。

2.2 scope.launch()的执行过程分析

CoroutineScope创建完成后,需要通过launch函数来执行协程代码,下面分析launch函数的执行过程。

    scope.launch {
        print("this code execute in coroutine")
    }

launch后面的lambda就是我们协程,也是我们业务代码执行的代码块,下面看一下launch()函数

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

launch()是CoroutineScope的扩展方法,三个参数中,CoroutineContext和CoroutineStart都提供了默认的值,该方法最终返回一个Job。该方法首先通过newCoroutineContext()函数返回一个CoroutineContext,下面看一下newCoroutineContext()函数:

public actual fun CoroutineScope.newCoroutineContext(context: CoroutineContext): CoroutineContext {
    val combined = coroutineContext + context
    val debug = if (DEBUG) combined + CoroutineId(COROUTINE_ID.incrementAndGet()) else combined
    return if (combined !== Dispatchers.Default && combined[ContinuationInterceptor] == null)
        debug + Dispatchers.Default else debug
}

这个函数没有太多的逻辑,控制debug。CoroutineStart没有指定的情况下,默认的是CoroutineStart.DEFAULT,因此会进入coroutine = StandaloneCoroutine,接下来执行coroutine.start(start, coroutine, block),

    /**
     * Starts this coroutine with the given code [block] and [start] strategy.
     * This function shall be invoked at most once on this coroutine.
     *
     * First, this function initializes parent job from the `parentContext` of this coroutine that was passed to it
     * during construction. Second, it starts the coroutine based on [start] parameter:
     * 
     * * [DEFAULT] uses [startCoroutineCancellable].
     * * [ATOMIC] uses [startCoroutine].
     * * [UNDISPATCHED] uses [startCoroutineUndispatched].
     * * [LAZY] does nothing.
     */
    public fun <R> start(start: CoroutineStart, receiver: R, block: suspend R.() -> T) {
        initParentJob()
        start(block, receiver, this)
    }

接下来进入CoroutineStart的invoke()方法

public enum class CoroutineStart {

    ......
    
    public operator fun <R, T> invoke(block: suspend R.() -> T, receiver: R, completion: Continuation<T>): Unit =
        when (this) {
            DEFAULT -> block.startCoroutineCancellable(receiver, completion)
            ATOMIC -> block.startCoroutine(receiver, completion)
            UNDISPATCHED -> block.startCoroutineUndispatched(receiver, completion)
            LAZY -> Unit // will start lazily
        }

}

在launch()方法中,CoroutineStart默认是DEFAULT,接下来执行startCoroutineCancellable()

internal fun <R, T> (suspend (R) -> T).startCoroutineCancellable(
    receiver: R, completion: Continuation<T>,
    onCancellation: ((cause: Throwable) -> Unit)? = null
) =
    runSafely(completion) {
        createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellableWith(Result.success(Unit), onCancellation)
    }

private inline fun runSafely(completion: Continuation<*>, block: () -> Unit) {
    try {
        block()
    } catch (e: Throwable) {
        completion.resumeWith(Result.failure(e))
    }
}

最后

协程的内容很多,其中的suspend是很重要的一块,这块的实现逻辑还没有完全搞清楚,后期再来弥补这些内容。

### 协程的工作原理及其与迭代器、状态机的关系 #### 1. 协程的基本工作原理 协程是一种轻量级的线程替代方案,它允许函数在执行过程中暂停并保存当前状态,在后续某个时刻恢复执行而不丢失上下文。这种特性使得协程非常适合用于异步编程场景,尤其是在需要长时间等待资源(如网络请求或文件I/O)的情况下。 在许多现代框架中,例如Unity[^2] 和Kotlin Coroutines[^3],协程通过`yield`关键字或其他类似的控制流机制实现暂停功能。具体来说: - **暂停**:当遇到`yield`语句时,协程会将其当前状态保存下来,并返回给调用者。 - **恢复**:当外部条件满足(如计时结束或事件触发),协程可以从上次停止的地方继续执行。 这种方式的核心在于协程的状态管理机制[^1],类似于人类解决问题时的记忆能力——可以随时中断思考并将注意力转移到其他事情上,之后再无缝衔接回来。 --- #### 2. 迭代器的作用 迭代器是一个设计模式,旨在提供一种统一的方式来访问集合中的元素,而无需暴露底层的数据结构[^5]。虽然表面上看迭代器主要用于遍历容器,但它也可以用来模拟协程的行为。 在一个典型的迭代器实现中,每次调用都会返回下一个值,同时保留自身的内部状态。这实际上已经具备了协程的部分特征:即能够在多次调用之间保持局部变量和执行位置的信息。 因此,从某种意义上讲,简单的协程可以用迭代器来实现。例如,在Python中,生成器就是一个特殊的迭代器形式,支持通过`yield`表达式暂停和恢复执行流程。 ```python def simple_coroutine(): while True: received = yield print(f"Received value: {received}") coro = simple_coroutine() next(coro) # 启动协程 coro.send(42) # 发送数据到协程 ``` 上述代码展示了如何利用生成器作为基础构建块创建基本的协程逻辑。 --- #### 3. 状态机的角色 状态机是一组有限数量的状态以及定义这些状态间转换规则的概念模型。它可以被视作更复杂的版本,专门针对那些具有多个阶段或者分支路径的过程建模。 对于协程而言,其本质也是一种隐式的状态机。每当协程因`yield`指令进入挂起状态时,实际上是进入了新的“子状态”。等到重新激活后,则依据之前的记录跳转至对应的位置继续运行下去。 举个例子,假设我们有一个下载图片的任务分为三个步骤:“发起HTTP请求”,“读取响应体”以及“保存本地副本”。那么整个过程便能分解成如下所示的一个小型状态图: | 当前状态 | 下一动作 | |----------------|----------------------------| | 初始化 | 开始加载远程URL | | 正在接受数据包 | 缓存接收到的内容 | | 数据接收完毕 | 将缓冲区写入磁盘文件 | 每完成一步操作便会自动推进到下一环节直至最终目标达成为止。 --- #### 4. 不阻塞主线程的原因分析 传统多线程环境下面临着频繁切换带来的开销问题,而且容易引发死锁等问题;相比之下,基于单一线程之上的协作式调度方式则显得更加高效可靠[^4]。由于所有的协程都共享同一个CPU核心而非各自独占,所以它们之间的轮换成本极低。 更重要的是,只有当某项活动确实处于闲置期才会发生转移现象—这意味着其余部分依旧维持活跃运转态势不受干扰影响。如此以来既保障了整体性能又兼顾用户体验流畅度需求。 --- ### 总结 综上所述,协程凭借内置的状态管理和灵活的控制权交接手段实现了优雅高效的并发解决方案。与此同时,借助于迭代器这一工具简化了许多繁琐的手工编码任务;再加上背后隐藏起来的那个精妙绝伦的状态迁移体系共同构筑起了如今广泛应用于各类软件开发领域内的强大武器库。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值