일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
- BFS
- 투 포인터
- 스택
- 그리디
- 프로그래머스
- Effective Java
- 알고리즘
- 백준
- 구현
- dfs
- 수학
- 완전탐색
- mst
- Network
- 유니온 파인드
- swea
- 문자열
- 이분탐색
- java
- CS
- 세그먼트 트리
- 플로이드-와샬
- 백트래킹
- 후니의 쉽게 쓴 시스코 네트워킹
- 시뮬레이션
- JUnit 5
- 동적계획법
- 위상정렬
- Kotlin
- 에라토스테네스의 체
반갑습니다!
[Kotlin] Principle of Coroutine 본문
코루틴이 쓰레드를 대체할 수 있다는 것은 앞의 예제들을 학습하면서 알 수 있었다. 이번에는 코루틴이 실제로 어떻게 동작하는 것인지 알아보도록 하자.
CPS (Continuation Passing Style)
코루틴은 컴파일러에 의해서 CPS (Continuation Passing Style)로 변환된다.
fun postItem(item: Item) {
val token = requestToken()
val post = createPost(token, item)
processPost(post)
}
이런 형태의 코드가 있다고 하자. 이 코드는 내부적으로 다음과 같은 코드로 변환된다.
fun postItem(item: Item) {
requestToken { token ->
val post = createPost(token, item) // Continuation
processPost(post) // Continuation
}
}
CPS는 쉽게 말해서 콜백과 비슷한 형태라고 이해하면 된다.
How does it works?
그렇다면 순차적으로 작성한 코드가 어떻게 비동기적으로 동작하고, 중단했다가 다시 실행하는 등의 동작을 할 수 있는 것일까?
suspend fun createPost(token: Token, item: Item): Post { ... }
위와 같이 작성한 코틀린 코드는 역컴파일하게되면 아래와 같은 코드로 변환된다.
Object createPost(Token token, Item item, Continuation<Post> cont) { ... }
역컴파일되면 Continuation 객체가 매개변수로 추가되면서 CPS로 변환된다.
Labels
suspend fun postItem(item: Item) {
// LABEL 0
val token = requestToken()
// LABEL 1
val post = createPost(token, item)
// LABEL 2
processPost(post)
}
suspend
함수는 컴파일되면서 LABEL이 붙게 되는데, 코루틴은 함수가 제개될 수 있어야하기 때문에 그런 지점을 LABEL을 지정하는 것이다.
suspend fun postItem(item: Item) {
switch (label) {
case 0:
val token = requestToken()
case 1:
val post = createPost(token, item)
case 2:
processPost(post)
}
}
이런식으로 함수를 다시 제개할 수 있도록 변하는 것이다. 그리고 레이블이 완성되면 CPS로 변환되는데 다음과 같은 형태가 된다.
fun postItem(item: Item, cont: Continuation) {
val sm = object : CoroutineImpl { ... }
switch (sm.label) {
case 0:
requestToken(sm)
case 1:
createPost(token, item, sm)
case 2:
processPost(post)
}
}
Continuation 객체는 콜백 인터페이스 같은 것으로 제개해주는 인터페이스를 가진 객체이다. sm은 'State Machine' 을 의미한다. 각각의 함수를 호출할 때는 지금까지 했던 연산의 결과를 넘겨줘야하기 때문에 sm을 매개변수로 넘겨주게 되는데 결국 이 것은 Continuation이고 어떤 정보 값을 가진 형태로 전달되어 코루틴이 동작되는 것이다.
Callback
fun postItem(item: Item, cont: Continuation) {
val sm = cont as? ThisSM ?: object : ThisSM {
fun resume(...) {
postItem(null, this)
}
}
switch (sm.label) {
case 0:
sm.item = item
sm.label = 1
requestToken(sm)
case 1:
createPost(token, item, sm)
}
}
각각의 suspend
함수가 sm을 마지막 매개변수로 가져가고 각각의 case를 마치게되면 resume()
을 호출하게 된다. 결국, 각각의 case가 끝날 때마다 label 값을 증가시키고 resume()
을 통해 재귀호출하는 형태로 코루틴이 동작하는 것이다.
Decompile
실제로 코틀린 코드를 역컴파일해서 확인해보자.
Like the dream code
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch {
val userData = fetchUserData()
val userCache = cacheUserData(userData)
updateTextView(userCache)
}
}
suspend fun fetchUserData() = "user_name"
suspend fun cacheUserData(user: String) = user
fun updateTextView(user: String) = user
위와 같은 코루틴 코드가 있다. 이 코드를 자바 코드로 역컴파일해보면 다음과 같다 .
@Nullable
public static final Object fetchUserData(@NotNull Continuation $completion) { return "user_name"; }
@Nullable
public static final Object cacheUserData(@NotNull String user, @NotNull Continuation $completion) { ... }
@NotNull
public static final String updateTextView(@NotNull String user) {
Intrinsics.checkParameterIsNotNull(user, "user");
return user;
}
public final Object invokeSuspend(@NotNull Object $result) {
Object var10000;
label17: {
Object var5 = IntrinsicksKt.getCOROUTINE_SUSPENDED();
CoroutineScope $this$launch;
String userData;
switch(this.label) {
case 0:
ResultKt.throwOnFailure($result);
$this$launch = this.p$;
this.L$0 = $this$launch;
this.label = 1;
var10000 = Example_nomagic_01KT.fetchUserData(this);
if(var10000 == var5) {
return var5;
}
break;
case 1:
$this$launch = (CoroutineScope)this.L$0;
ResultKt.throwOnFailure($result);
var10000 = $result;
break;
case 2:
userData = (String)this.L$1;
$this$launch = (CoroutineScope)this.L$0;
ResultKt.throwOnFailure($result);
var10000 = $result;
break label17;
...
}
}
}
실제로도 위에서 설명한 방식처럼 역컴파일 된다는 것을 알 수 있다.
'Kotlin' 카테고리의 다른 글
[Kotlin] Coroutine Context and Dispatchers (0) | 2020.07.05 |
---|---|
[Kotlin] Composing Suspending Functions (0) | 2020.07.05 |
[Kotlin] Cancellation and Timeouts (0) | 2020.07.04 |
[Kotlin] Coroutine Basics (0) | 2020.07.04 |
[Kotlin] Coroutine Guide (0) | 2020.07.04 |