Kotlin:深度理解协程挂起恢复实现原理。纯源码分析。

本文详细分析了Kotlin协程的挂起、恢复和启动过程。从一个简单的协程示例出发,探讨了挂起函数的实现,包括其在Java中的反编译形式,以及如何通过Continuation实现挂起和恢复。接着,分析了协程的启动流程,解释了如何通过startCoroutine和createCoroutineUnintercepted触发协程执行,并在挂起和恢复时如何利用状态机切换。最后,总结了协程的核心机制,强调了编译器在其中的作用以及挂起函数的返回值特点。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言👀

原文传送门➡️➡️➡️➡️➡️➡️➡️➡️➡️➡️

接触过kotlin协程的小伙伴是不是都有疑问。

  • 协程是依靠什么实现的呢?
  • 如何挂起?
  • 如何恢复?

博主带你深度理解kotlin协程的挂起和恢复。

分析对象👀

不用协程库能不能启动协程呢?当然是可以的。

举个🌰,我们可以这么写

fun main() {
    suspend {
        log("coroutine start")
        val value = suspendFunction1()
        log(value)
        log("suspendFunction----执行之后")
        "1"
    }.startCoroutine(object : Continuation<Any?> {
        override val context: CoroutineContext
            get() = EmptyCoroutineContext

        override fun resumeWith(result: Result<Any?>) {
            log(result.getOrNull())
        }

    })
}
suspend fun suspendFunction1() = suspendCoroutine<Int> { continuation ->
    val threadGroup = ThreadGroup("thread-suspend-1")
    val threadExecutor = Executors.newSingleThreadExecutor {
        Thread(threadGroup, it, "thread")
    }
    threadExecutor.submit {
        Thread.sleep(2000)
        continuation.resume(0)
    }
}
//执行结果
09:05:44:934 [main] coroutine start
09:05:46:960 [thread] 0
09:05:46:961 [thread] suspendFunction----执行之后
09:05:46:961 [thread] 1

协程框架的launchasync aswaitrunBlocking等等对上述的封装,使用起来更加顺手而已。

从执行的结果来看,先输出了coroutine start,之后挂起两秒钟,继续执行之后的逻辑。

那我们就用这个demo来分析,协程的挂起与恢复是如何实现的。😮

挂起函数分析👀

首先我们需要分析一下挂起函数是个什么玩意儿~😮

将上述suspendFunction1挂起函数代码转换成java看看。

   public static final Object suspendFunction1(@NotNull Continuation $completion) {
      ...
      SafeContinuation var4 = new SafeContinuation(IntrinsicsKt.intercepted($completion));
      Continuation continuation = (Continuation)var4;
      int var6 = false;
      ThreadGroup threadGroup = new ThreadGroup("thread-suspend-1");
      ExecutorService threadExecutor = Executors.newSingleThreadExecutor((ThreadFactory)(new DeepUnderstandCoroutineKt$suspendFunction1$2$threadExecutor$1(threadGroup)));
      threadExecutor.submit((Runnable)(new DeepUnderstandCoroutineKt$suspendFunction1$2$1(continuation)));
      Object var10000 = var4.getOrThrow();
      if (var10000 == IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
         DebugProbesKt.probeCoroutineSuspended($completion);
      }
      return var10000;
   }
final class DeepUnderstandCoroutineKt$suspendFunction1$2$1 implements Runnable {
   // $FF: synthetic field
   final Continuation $continuation;

   public final void run() {
      Thread.sleep(2000L);
      Continuation var1 = this.$continuation;
      Integer var2 = 0;
      boolean var3 = false;
      Companion var4 = Result.Companion;
      boolean var5 = false;
      var1.resumeWith(Result.constructor-impl(var2));
   }

   DeepUnderstandCoroutineKt$suspendFunction1$2$1(Continuation var1) {
      this.$continuation = var1;
   }
}

​ 看得出,suspend函数反编译之后,会自动将一个Continuation作为参数传递进函数,且返回类型为object。😮

​ 简单看一下,将Continuation作为参数传递给了IntrinsicsKt.intercepted($completion)IntrinsicsKtjava平台的具体实现在IntrinsicsJvm文件中。而intercepted的源码如下所示:

public actual fun <T> Continuation<T>.intercepted(): Continuation<T> =
    (this as? ContinuationImpl)?.intercepted() ?: this

​ 这里不再深追,如果您熟悉协程框架的话,这里其实返回了Continuation的拦截器,依旧是Continuation类型,如果不存在拦截器,其实还是返回的自身。我们上面的写法,没有拦截器,返回的就是自身。最终将返回的Continuation传递给了SafeContinuation

​ 省略掉线程池的代码,看到最后调用了SafeContinuationgetOrThrow函数,我们来看一下这个代码(具体实现在SafeContinuationJvm文件):😮

    internal actual class SafeContinuation<in T>
internal actual constructor(
    private val delegate: Continuation<T>,
    initialResult: Any?
) : Continuation<T>, CoroutineStackFrame {
    @PublishedApi
    internal actual constructor(delegate: Continuation<T>) : this(delegate, UNDECIDED)
    ...
    @Volatile
    private var result: Any? = initialResult
    
    internal actual fun getOrThrow(): Any? {
        var result = this.result // atomic read
        if (result === UNDECIDED) {
            if (RESULT.compareAndSet(this, UNDECIDED, COROUTINE_SUSPENDED)) return COROUTINE_SUSPENDED
            result = this.result // reread volatile var
        }
        ...
    }

​ 先简单看一下SafeContinuation构造器,我们会调用它的副构造器,将**continuation赋值给delegate。同时赋值initialResultUNDECIDED**。所以此时result == UNDECIDED

​ 所以调用getOrThrow之后,就会走if (RESULT.compareAndSet(this, UNDECIDED, COROUTINE_SUSPENDED)) return COROUTINE_SUSPENDEDresult设置为COROUTINE_SUSPENDED,同时返回COROUTINE_SUSPENDED

​ 所以最终suspendFunction1函数会返回COROUTINE_SUSPENDED标志,代表挂起

​ 接下来我们分析一下传递给线程执行的代码如何走到恢复呢?我们看一下run()里面的代码,其实就是在恢复的时候,调用了SafeContinuationresumeWith函数,我们看一下resumeWith的代码:

    public actual override fun resumeWith(result: Result<T>) {
        while (true) { // lock-free loop
            val cur = this.result // atomic read
            when {
                cur === UNDECIDED -> if (RESULT.compareAndSet(this, UNDECIDED, result.value)) return
                cur === COROUTINE_SUSPENDED -> if (RESULT.compareAndSet(this, COROUTINE_SUSPENDED, RESUMED)) {
                    delegate.resumeWith(result)
                    return
                }
                else -> throw IllegalStateException("Already resumed")
            }
        }
    }

​ 还记得上面写道的,result被设置为了COROUTINE_SUSPENDED。所以此时会走delegate.resumeWith(result)这个代码,同时将result设置为了RESUMED

​ 也就是说会通过真正的、被传进函数的原始continuation去执行恢复操作。

​ 好了suspend函数,就先分析到这里,接下来我们看真正的挂起和恢复如何被执行的!🙆‍♀️

启动分析👀

​ 上面分析了挂起函数,这里具体看一下协程是如何启动执行的。看一下反编译后的代码:

public static final void main() {
      Function1 var0 = (Function1)(new Function1((Continuation)null) {
         int label;

         @Nullable
         public final Object invokeSuspend(@NotNull Object $result) {
            Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
            Object var10000;
            switch(this.label) {
            case 0:
               ResultKt.throwOnFailure($result);
               LogKt.log(new Object[]{"coroutine start"});
               this.label = 1;
               var10000 = DeepUnderstandCoroutineKt.suspendFunction1(this);
               if (var10000 == var3) {
                  return var3;
               }
               break;
            case 1:
               ResultKt.throwOnFailure($result);
               var10000 = $result;
               break;
            default:
               throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
            }

            int value = ((Number)var10000).intValue();
            LogKt.log(new Object[]{Boxing.boxInt(value)});
            LogKt.log(new Object[]{"suspendFunction----执行之后"});
            return "1";
         }

         @NotNull
         public final Continuation create(@NotNull Continuation completion) {
            Intrinsics.checkNotNullParameter(completion, "completion");
            Function1 var2 = new <anonymous constructor>(completion);
            return var2;
         }

         public final Object invoke(Object var1) {
            return ((<undefinedtype>)this.create((Continuation)var1)).invokeSuspend(Unit.INSTANCE);
         }
      });
      boolean var1 = false;
      ContinuationKt.startCoroutine(var0, (Continuation)(new Continuation() {
         @NotNull
         public CoroutineContext getContext() {
            return (CoroutineContext)EmptyCoroutineContext.INSTANCE;
         }

         public void resumeWith(@NotNull Object result) {
            Object[] var10000 = new Object[1];
            boolean var3 = false;
            var10000[0] = Result.isFailure-impl(result) ? null : result;
            LogKt.log(var10000);
         }
      }));
   }

有点长,不着急,一点一点看~🙆‍♀️

整体看一下,看得出suspend{}代码块被转换为了Function1,传入null,又作为参数传入startCoroutine函数用于协程的启动。startCoroutine函数,另外接受一个Continuation用于协程结束时进行调用。

另外我们再看一下有这么一行函数DeepUnderstandCoroutineKt.suspendFunction1(this),调用了我们的挂起函数,传递进去的是Funcation1类型的。从上面分析知道,挂起函数接收的类型是Continuation。所以Funcation1应该是Continuation类型的。那具体是那个类型呢?我们看一下生成的字节码,有这么一行。

final class org/example/zxf/kotlin11/DeepUnderstandCoroutineKt$main$1 extends kotlin/coroutines/jvm/internal/SuspendLambda implements kotlin/jvm/functions/Function1 {


  // access flags 0x11
  public final invokeSuspend(Ljava/lang/Object;)Ljava/lang/Object;

所以这玩意其实是SuspendLambda

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
}

玩意实现了ContinuationImpl,ContinuationImpl实现了BaseContinuationImpl。这里介绍一下,后续分析需要这些类。

启动的话,调用了startCoroutine

public fun <T> (suspend () -> T).startCoroutine(
    completion: Continuation<T>
) {
    createCoroutineUnintercepted(completion).intercepted().resume(Unit)
}
public inline fun <T> Continuation<T>.resume(value: T): Unit =
    resumeWith(Result.success(value))

最后调用到了ContinuationresumeWith函数。

通过上面的分析我们知道,它的receiver其实是suspend{},也就是SuspendLambda,它的resumeWith函数在BaseContinuationImpl进行了实现,我们看一下代码。

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
                }
            }
        }
    }

我们代码,最后一路调用到了invokeSuspend,这玩意不就是反编译后的invokeSuspend嘛,所以这时候就会执行到suspend{}代码块里面。协程启动

挂起分析👀

调用到invokeSuspend之后协程正式启动,为了分析方便,我把反编译之后的invokeSuspend函数内的代码再次拉出来,如下所示:

Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
Object var10000;
switch(this.label) {
    case 0:
        ResultKt.throwOnFailure($result);
        LogKt.log(new Object[]{"coroutine start"});
        this.label = 1;
        var10000 = DeepUnderstandCoroutineKt.suspendFunction1(this);
        if (var10000 == var3) {
        return var3;
    }
    	break;
    case 1:
        ResultKt.throwOnFailure($result);
        var10000 = $result;
        break;
    default:
    	throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
    }

int value = ((Number)var10000).intValue();
LogKt.log(new Object[]{Boxing.boxInt(value)});
LogKt.log(new Object[]{"suspendFunction----执行之后"});
return "1";

接下来就是状态机的分析了,label默认是0,所以会执行case 0,打印coroutine startlabel 赋值为1 ,调用DeepUnderstandCoroutineKt.suspendFunction1(this)通过上面的分析我们知道,它会返回COROUTINE_SUSPENDED标志位。var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED()就等于COROUTINE_SUSPENDED,此时判断相等。返回退出invokeSuspend函数,表现为,退出了suspend{}代码块,协程挂起

恢复分析👀

接下来分析恢复,恢复的时候,经过上面对挂起函数的分析我们知道,最后还是调用到了resumeWith进而调用到了invokeSuspend。因为这时候label == 1,所以执行ResultKt.throwOnFailure($result)获取到值,最后breakswitch

进而执行后续写在suspend{}的剩余代码,打印获取到的值,以及打印suspendFunction----执行之后。然后返回了1

那么返回的1,如何执行到了,startCoroutine里面新建的continuation了呢?调用startCoroutine其实会调用到IntrinsicsJvmcreateCoroutineUnintercepted,里面有这么一行代码。

create(receiver, probeCompletion)

而我们反编译suspend{}后,看得出有create函数,长这个样子

public final Continuation create(@NotNull Continuation completion) {
    Intrinsics.checkNotNullParameter(completion, "completion");
    Function1 var2 = new <anonymous constructor>(completion);
    return var2;
}

所以在此时,会将completion作为参数,传递给了SuspendLambda。而在BaseContinuationImplresumeWith中,有这个逻辑。

val outcome: Result<Any?> =
try {
    val outcome = invokeSuspend(param)
    if (outcome === COROUTINE_SUSPENDED) return
    Result.success(outcome)
} catch (exception: Throwable) {
    Result.failure(exception)
}
    ...
} else {
    // top-level completion reached -- invoke and return
    completion.resumeWith(outcome)
    return
}

所以,会将1调用到completion中去。

总结👀

​ kotlin协程的实现,大部分依赖编译器来着,比如悄悄的传递continuation,状态机switch代码的生成等等。它其实也是按顺序执行,只是根据具体挂起恢复业务不同,执行suspendInvoke函数的分支不一样而已;

​ 比如,我再加一个挂起函数,生成的代码就不一样了

			//给suspend代码块里面新增一个挂起函数suspendFunction2
			suspend {
                log("coroutine start")
                val value = suspendFunction1()
                suspendFunction2()
                log(value)
                log("suspendFunction----执行之后")
                "1"
   			 }
			//状态机的代码不一样了
			switch(this.label) {
               case 0:
                  ResultKt.throwOnFailure($result);
                  LogKt.log(new Object[]{"coroutine start"});
                  this.label = 1;
                  var10000 = DeepUnderstandCoroutineKt.suspendFunction1(this);
                  if (var10000 == var3) {
                     return var3;
                  }
                  break;
               case 1:
                  ResultKt.throwOnFailure($result);
                  var10000 = $result;
                  break;
               case 2:
                  value = this.I$0;
                  ResultKt.throwOnFailure($result);
                  break label17;
               default:
                  throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
               }

               value = ((Number)var10000).intValue();
               this.I$0 = value;
               this.label = 2;
               if (DeepUnderstandCoroutineKt.suspendFunction2(this) == var3) {
                  return var3;
               }

​ 一个协程suspend{}在运行中,会产生常见两个continuation,一个作为参数传递给各个挂起函数,用于恢复;另一个作为协程结束最后的回调;

​ 另外对于挂起函数来说,如果object返回值,如果返回的是COROUTINE_SUSPENDED则代表挂起,返回其他则代表是正常的返回值。

相关源码实现文件列举:🙆‍♀️

ContinuationImplIntrinsicsJvmSafeContinuationJvm

如果存在什么问题,欢迎指正哈。欢迎技术交流!🙆‍♀️

创作不易,觉得不错点个赞~🙆‍♀️

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

pumpkin的玄学

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值