Kotlin协程的运行原理

我在另一篇博客 Kotlin的协程 已经介绍了关于协程的很多东西,也是这一篇能看懂的基础。这一篇专门用一个示例来讲解一下它的底层运行机制。

在一个KotlinShow.kt文件里有如下代码

复制代码
suspend fun example() {
    println("start")
    delay(1000)   // 挂起点1
    println("middle")
    delay(500)    // 挂起点2
    println("end")
}

@OptIn(DelicateCoroutinesApi::class)
fun main() = runBlocking {
    val job = GlobalScope.launch {
        example()
    }
    job.join()
}

使用IDEA的decompile后看到的代码

复制代码
IDEA里decompile后的代码如下。为方便指定位置,我在下面的代码里插入了注释,如第A处、第B处等。

@Metadata(
   mv = {2, 2, 0},
   k = 2,
   xi = 48,
   d1 = {"\u0000\n\n\u0000\n\u0002\u0010\u0002\n\u0002\b\u0003\u001a\u000e\u0010\u0000\u001a\u00020\u0001H\u0086@¢\u0006\u0002\u0010\u0002\u001a\u0006\u0010\u0003\u001a\u00020\u0001¨\u0006\u0004"},
   d2 = {"example", "", "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", "main", "Sources of Practice"}
)

public final class KotlinShowKt {
   @Nullable
   public static final Object example(@NotNull Continuation $completion) {
      Continuation $continuation;
      label27: {
         if ($completion instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)$completion;
            if (($continuation.label & Integer.MIN_VALUE) != 0) {
               $continuation.label -= Integer.MIN_VALUE;
               break label27;
            }
         }

         $continuation = new ContinuationImpl($completion) {
            // $FF: synthetic field
            Object result;
            int label;

            //第J处
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               return KotlinShowKt.example((Continuation)this);
            }
         };
      }

      label22: {
         Object $result = $continuation.result;
         Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
         switch ($continuation.label) {
		    //第A处
            case 0:
               ResultKt.throwOnFailure($result);
               System.out.println("start");
               $continuation.label = 1;
               if (DelayKt.delay(1000L, $continuation) == var3) {
                  return var3;
               }
               break;
            case 1:
               ResultKt.throwOnFailure($result);
               break;
            case 2:
               ResultKt.throwOnFailure($result);
               break label22;
            default:
               throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
         }

         System.out.println("middle");
         $continuation.label = 2;
		 
		 //第B处
         if (DelayKt.delay(500L, $continuation) == var3) {
            return var3;
         }
      }

      System.out.println("end");
      return Unit.INSTANCE;
   }

   public static final void main() {
      BuildersKt.runBlocking$default((CoroutineContext)null, new Function2((Continuation)null) {
         Object L$0;
         int label;

         //第C处
         public final Object invokeSuspend(Object $result) {
            Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
            switch (this.label) {
               case 0:
                  ResultKt.throwOnFailure($result);
				  
				  //第D处
                  Job job = BuildersKt.launch$default((CoroutineScope)GlobalScope.INSTANCE, (CoroutineContext)null, (CoroutineStart)null, new Function2((Continuation)null) {
                     int label;

                     //第E处
                     public final Object invokeSuspend(Object $result) {
                        Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
                        switch (this.label) {
						   //第F处
                           case 0:
                              ResultKt.throwOnFailure($result);
                              Continuation var10000 = (Continuation)this;
                              this.label = 1;
							  //第G处
                              if (KotlinShowKt.example(var10000) == var2) {
                                 return var2;
                              }
                              break;
                           case 1:
                              ResultKt.throwOnFailure($result);
                              break;
                           default:
                              throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
                        }

                        return Unit.INSTANCE;
                     }

                     public final Continuation create(Object value, Continuation $completion) {
                        return (Continuation)(new <anonymous constructor>($completion));
                     }

                     public final Object invoke(CoroutineScope p1, Continuation p2) {
                        return ((<undefinedtype>)this.create(p1, p2)).invokeSuspend(Unit.INSTANCE);
                     }

                     // $FF: synthetic method
                     // $FF: bridge method
                     public Object invoke(Object p1, Object p2) {
                        return this.invoke((CoroutineScope)p1, (Continuation)p2);
                     }
                  }, 3, (Object)null);
                  Continuation var10001 = (Continuation)this;
                  this.L$0 = SpillingKt.nullOutSpilledVariable(job);
                  this.label = 1;
				  //第H处
                  if (job.join(var10001) == var3) {
                     return var3;
                  }
                  break;
               case 1:
                  Job job = (Job)this.L$0;
                  ResultKt.throwOnFailure($result);
                  break;
               default:
                  throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
            }

            return Unit.INSTANCE;
         }

         public final Continuation create(Object value, Continuation $completion) {
            return (Continuation)(new <anonymous constructor>($completion));
         }

         public final Object invoke(CoroutineScope p1, Continuation p2) {
            return ((<undefinedtype>)this.create(p1, p2)).invokeSuspend(Unit.INSTANCE);
         }

         // $FF: synthetic method
         // $FF: bridge method
         public Object invoke(Object p1, Object p2) {
            return this.invoke((CoroutineScope)p1, (Continuation)p2);
         }
      }, 1, (Object)null);
   }

   // $FF: synthetic method
   public static void main(String[] args) {
      main();
   }
}

在反编译出的代码中可以看到example()被编译成了一个static方法。经过多次尝试,我发现先把眼光聚焦在这个example()上更容易把协程讲清楚。咱们先不管程序到底是怎样启动起来的,现在咱们只需知道example()方法要第一次执行了。

可以看到在执行example()时会传进来一个Continuation,然后又用它生成了一个ContinuationImpl,再往下就会执行到第A处代码,其中有delay,但它并不会真的要等多少ms才能返回结果和var3判断是否相等,它会立即返回结果,所以这里其实马上就和var3做了是否相等的判断,首次执行时为true,所以这里很快就返回了一个SUSPENDED的结果,example()首次执行也就结束了。

delay为什么能马上就返回呢?因为它相当于提交了一个定时任务,但是并不由它去执行这个定时任务,定时任务由调度器去执行,delay()提交之后就马上返回了,再看下它的第二个参数DelayKt.delay(1000L, $continuation),

$continuation ---

continuation = new ContinuationImpl(completion) {

// $FF: synthetic field

Object result;

int label;

//第J处

@Nullable

public final Object invokeSuspend(@NotNull Object $result) {

this.result = $result;

this.label |= Integer.MIN_VALUE;

return KotlinShowKt.example((Continuation)this);

}

};

调度器方面大致如下

scheduler.schedule(1000ms, continuation)

当定时到达后,调度器里面就会调用$continuation的resumeWith() ---> invokeSuspend(),example()就会又被调用,但此时lable已不是0了,这就很清晰地展现出了协程的机制------状态驱动。每次传给example()的Continuation不一样,example()也就会有不同的逻辑,当最后一个Continuation处理完,example()的整个逻辑也就执行完毕了。每次传进来的Continuation是 恢复执行的上下文 + 入口 。

理清了example()的执行规律,再类推外层的逻辑,对于嵌套型的协程也就更容易理解了。

main()中用BuildersKt.runBlocking$default(编译后的带默认参数的runBlocking)启动了协程,通过查询API的调用流程就可以发现最后经过 BaseContinuationImpl.resumeWith() 调用了 main()中的

public final Object invokeSuspend(Object $result),

即 第C处invokeSuspend(代码中有标记注释)

此时lable为默认值0,所以程序进入了case 0分支,其中的 第D处 代码

Job job = BuildersKt.launch$default((CoroutineScope)GlobalScope.INSTANCE... 就是源代码中的 GlobalScope.launch(),launch只是提交一个协程,并不一定会立即就能执行,所以这个方法会立即返回,invokeSuspend会继续往下走,直到第H处 job.join() ,join()会将launch协程挂起,注意:join()也是一个suspend函数,它会立即返回,由于返回的是"挂起",所以launch此次的invokeSuspend就会马上执行结束。同样查询API,又可以发现这个launch最终又是通过resumeWith()走到了 第E处invokeSuspend,同样,label为0,第G处代码触发example()执行。那么example()第一次执行时的Continuation是谁传进去的呢?正是launch的,launch的Continuation又是哪里来的呢?是runBlocking的,这样Continuation就把多处协程串联起来了。当example()彻底执行完毕,调度器就会通过Continuation触发launch再次执行,以此类推,直至最外层协程执行完毕。调度器总是不停触发最底层的Continuation来推动协程从最底层层层向上完成。

join() 本身不等待。它只看一眼 job 是否已完成:

如果已完成 → 立刻返回,啥也不干。

如果未完成 → 把当前协程的"剩余代码"打包成一个回调,注册到 job 上,然后 立刻返回一个"我已挂起"的信号。

挂起就是提前返回。join() 返回挂起信号后,外层调用 join() 的那个函数(协程的状态机)也立刻返回。整个协程的执行函数走完了这一次调用,不再占用任何线程。

恢复靠回调。当 job 执行完毕时,它触发之前注册的回调,这个回调会 再次进入 协程的状态机,从上次挂起的地方继续往下执行。

相关推荐
隐退山林1 小时前
JavaEE进阶:SpringIoC&DI
java·开发语言·java-ee
水煮白菜王1 小时前
Claude Code 全方位使用手册
java·开发语言·网络
Highcharts.js1 小时前
金融Web App中的复杂时序数据可视化:从选型到高性能实践
开发语言·金融·highcharts·实战代码·响应式图表
郝学胜-神的一滴1 小时前
跨平台 C++ 静态库编译实战:Linux/Windows/macOS 三端统一实现
linux·开发语言·c++·windows·软件构建
xyq20241 小时前
XHR 请求详解
开发语言
ooseabiscuit1 小时前
Laravel10.x重磅发布:新特性全解析
android·java·开发语言·mysql
沐知全栈开发1 小时前
JavaScript while 循环详解
开发语言
svdo1250p1 小时前
“Fatal error: require(): Failed opening required...” 以及如何彻底避免它再次出现
android·ide·android studio
ch.ju1 小时前
Java程序设计(第3版)第三章——数组
java·开发语言