티스토리 뷰

코틀린 내부 동작 방식 이해하기

- 코틀린 코루틴 책 4장의 주제를 정리

 

코루틴이 쓰인 코루틴 코드

fun main() {
  CoroutineScope(Dispatchers.Default).launch {
    val token = fetchToken()
    println(token)
  }.join()
}

suspend fetchToken(){
  delay(1000)
  return "1234"
}

자바 디컴파일로 변환 했을 때의 코드를 중요한 부분만 적은 수도코드

(코틀린, 자바 짬뽕인 수도코드)

fun main(completion:Continuation) {
  var var10000 = BuildersKt.launch$default(/** ... */, Function2() {
    int label = 0
    fun invoke(var1, var2) {
      // ...
      return create(var1,var2).invokeSuspend(Unit.INSTANCE)
    }

    fun create(var1, var2){
      // ...
      return var2 as Fuction2
    }
  })

  fun invokeSupsend(result: Object) {
    Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
    Object var10000
    switch(label) {
        0: {
          label = 1
          var10000 = fetchToken(this)
          if(var10000 == var3) // suspend라면 리턴
            return var3
          break;
        }
        1: {
          var10000 = result
          break;
        }
    }
    val res = var10000
    println(res)
    return Unit.INSTANCE
  }
}

fun fetchToken(var3:Continuation) {
  Object continuation;
  // continuation을 초기화 하기 위한 처리가 있음
  if(continuation이 초기화가 필요하면) {
    continuation = Continuation(var3){
      Object result
      Object label
      fun invokeSuspend(Object result) {
        this.result = result
        label = 값 조작
        return fetchToken(this)
      }
    }
  }

  val label = continuation.label
  Object result = continuation.result
  val var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
  switch(label) {
    0: {
      continuation.label  = 1
      if(delay(1000, continuation) == IntrinsicsKt.getCOROUTINE_SUSPENDED()){
        return var3
      }
      break;
    }
    1: {
      break;
    }
  }
  return "1234"
}

순서

코루틴실행

  • main안의 Function2의 invoke -> create -> invokeSuspsend
  • fetchToken
    • delay를 만나면 그대로 리턴됨

suspend되어서 함수들의 리턴이 발생

  • 기존 함수 콜스택과 똑같음
  • fetchToken 리턴 -> main의 invokeSuspend 리턴 -> invoke 리턴 -> main함수 리턴

🔆[중요] delay 후 코루틴 resume🔆

  • fetchToken의 continuation의 invokeSusppend 실행
    • fetchToken 실행
    • fetchToken은 label1단계라서 1234를 리턴
    • continuation의 invokeSupspend의 리턴은 1234
  • main함수의 invokeSupsend 실행
    • invokeSupsend의 아큐먼트는 fetchToken의 continuation의 invokeSuspend의 리턴값. 즉. 1234
    • res변수의 값은 1234가 됨

 

 

추가적으로, 디컴파일 코드 원문

// Main3Kt.java
package com.heenu.yoonnote.screen;

import kotlin.Metadata;
import kotlin.ResultKt;
import kotlin.Unit;
import kotlin.coroutines.Continuation;
import kotlin.coroutines.CoroutineContext;
import kotlin.coroutines.intrinsics.IntrinsicsKt;
import kotlin.coroutines.jvm.internal.ContinuationImpl;
import kotlin.coroutines.jvm.internal.RunSuspendKt;
import kotlin.jvm.functions.Function2;
import kotlin.jvm.internal.Intrinsics;
import kotlinx.coroutines.BuildersKt;
import kotlinx.coroutines.CoroutineScopeKt;
import kotlinx.coroutines.CoroutineStart;
import kotlinx.coroutines.DelayKt;
import kotlinx.coroutines.Dispatchers;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

@Metadata(
   mv = {1, 8, 0},
   k = 2,
   d1 = {"\u0000\u0010\n\u0000\n\u0002\u0010\u000e\n\u0002\b\u0002\n\u0002\u0010\u0002\n\u0000\u001a\u0011\u0010\u0000\u001a\u00020\u0001H\u0086@ø\u0001\u0000¢\u0006\u0002\u0010\u0002\u001a\u0011\u0010\u0003\u001a\u00020\u0004H\u0086@ø\u0001\u0000¢\u0006\u0002\u0010\u0002\u0082\u0002\u0004\n\u0002\b\u0019¨\u0006\u0005"},
   d2 = {"fetchToken", "", "(Lkotlin/coroutines/Continuation;)Ljava/lang/Object;", "main", "", "app_debug"}
)
public final class Main3Kt {
   @Nullable
   public static final Object main(@NotNull Continuation $completion) {
      Object var10000 = BuildersKt.launch$default(CoroutineScopeKt.CoroutineScope((CoroutineContext)Dispatchers.getDefault()), (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((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);
                  this.label = 1;
                  var10000 = Main3Kt.fetchToken(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");
            }

            String res = (String)var10000;
            System.out.println(res);
            return Unit.INSTANCE;
         }

         @NotNull
         public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
            Intrinsics.checkNotNullParameter(completion, "completion");
            Function2 var3 = new <anonymous constructor>(completion);
            return var3;
         }

         public final Object invoke(Object var1, Object var2) {
            return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
         }
      }), 3, (Object)null).join($completion);
      return var10000 == IntrinsicsKt.getCOROUTINE_SUSPENDED() ? var10000 : Unit.INSTANCE;
   }

   // $FF: synthetic method
   public static void main(String[] var0) {
      RunSuspendKt.runSuspend(new Main3Kt$$$main(var0));
   }

   @Nullable
   public static final Object fetchToken(@NotNull Continuation var0) {
      Object $continuation;
      label20: {
         if (var0 instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)var0;
            if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
               ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
               break label20;
            }
         }

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

            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               return Main3Kt.fetchToken(this);
            }
         };
      }

      Object $result = ((<undefinedtype>)$continuation).result;
      Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      switch (((<undefinedtype>)$continuation).label) {
         case 0:
            ResultKt.throwOnFailure($result);
            ((<undefinedtype>)$continuation).label = 1;
            if (DelayKt.delay(1000L, (Continuation)$continuation) == var3) {
               return var3;
            }
            break;
         case 1:
            ResultKt.throwOnFailure($result);
            break;
         default:
            throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }

      return "1234";
   }
}
// Main3Kt$$$main.java
package com.heenu.yoonnote.screen;

import kotlin.Metadata;
import kotlin.coroutines.Continuation;
import kotlin.jvm.functions.Function1;
import kotlin.jvm.internal.Lambda;

// $FF: synthetic class
@Metadata(
   mv = {1, 8, 0},
   k = 3
)
final class Main3Kt$$$main extends Lambda implements Function1 {
   // $FF: synthetic field
   private final String[] args;

   // $FF: synthetic method
   Main3Kt$$$main(String[] var1) {
      super(1);
      this.args = var1;
   }

   // $FF: synthetic method
   public final Object invoke(Object var1) {
      return Main3Kt.main((Continuation)var1);
   }
}

 

 

 

 

비트연산 코드

  • 보다보면 비트연산이 3개가 나옴
(((<undefinedtype>)$continuation).label & Integer.MIN_VALUE)
 ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
this.label |= Integer.MIN_VALUE;

 

1번째 코드

(((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0
  • Integer.MIN_VALUE : 1000 0000 0000 0000 0000 0000 0000 0000
  • & 연산자는 두 비트 모두 1일 때만 결과가 1이 되고, 그렇지 않으면 0.
  • (((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0
    • label의 최상위 비트를 체크
    • 코루틴이 중단 될때 최상위 비트를 1로 설정
    • 즉, 결과가 1이라면 이미 중단된 상태라는 뜻
    • 즉, (((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) = 1 이라서 저 조건문은 true

 

2번째 코드

((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
  • label의 최상위 비트를 0으로 만듬

3번째 코드

this.label |= Integer.MIN_VALUE;
  • label의 최상위 비트를 1로 만듬

 

[정리]

  • 코루틴이 중단되었는지 파악하기 위해서 비트연산자를 사용
  • continution.label이 1이면 중단된 상태

'개발 > 코틀린' 카테고리의 다른 글

변성 - 공변, 무공변, 반공변  (0) 2020.11.22