-
Kotlin Coroutine Basic프로그래밍/Kotlin 2018. 11. 4. 18:28반응형
이러한 극단적인 콜백지옥 코드를
fun requestTokenAsync(db:(Token) -> Unit) {} fun createPostAsync(token:Token, item:Item, cb:(Post)->Unit) {} fun processpost(post:Post) {} fun postItem(item:Item) { requestTokenAsync { token -> createPostAsync(token, item) { post -> processPost(post) } } }
코루틴을 사용하면 아래와 같이 바꿀 수 있다. (참조 : https://www.youtube.com/watch?v=_hfBv0a09Jc)
suspend fun requestToken(): Token{} suspend fun createPost(token:Token, item:Item): Post{} fun processPost(post:Post) {} suspend fun postItem(item:Item) { val token = requestToken() val post = createpost(token, item) processPost(post) }
위 처럼 생긴 코드를 코루틴 이라고 한다.
cooperative routine 의 약자이다. 협력하는 루틴 이라는 의미인데, 좀 더 자세한 의미와 몇 가지 기능을 알아보고자 한다.
코루틴
코틀린 1.3 버전에 포함된 kotlinx.coroutines.* 패키지이다. 다른 언어들에도 있는 일반적인 개념이다.
파이썬에서는 코루틴의 동작은 아래와 같다. 이해하기 좋은 자료가 있어서 가져왔다. (https://dojang.io/mod/page/view.php?id=1122)
우선 코루틴을 이해하기 위해서는 메인-서브 루틴의 관계에 대해서 알고 있어야한다.
def add(a, b): c = a + b # add 함수가 끝나면 변수와 계산식은 사라짐 print(c) print('add 함수') def calc(): add(1, 2) # add 함수가 끝나면 다시 calc 함수로 돌아옴 print('calc 함수') calc()
calc() 를 메인 루틴(main routine)이라고 하고 add() 를 calc()의 서브 루틴(sub routine) 이라고 한다. 메인 루틴은 서브 루틴을 실행하며 서브 루틴은 실행 후 종료되고 그 상태 값을 잊어버린다. 즉 서브 루틴은 메인 루틴에 종속적인 관계입니다.
하지만 코루틴은 메인-서브 루틴의 종속적인 관계와 다르게
1) 서로 협력(cooperative)하는 대등한 관계이고
2) 마지막 줄의 코드를 실행하더라도 상태값을 유지하며
3) 특정 시점에 상대방의 코드를 실행할 수 있습니다.
https://en.wikipedia.org/wiki/Coroutine
코루틴은 다른 코루틴에 의해서 실행될 수 있다. 다른 코루틴에 의해서 실행되면 그 루틴을 나갈 수 있고 후에 호출했던 시점으로 다시 돌아올 수도 있고 돌아오지 않을 수도 있다. 또한, 코루틴은 종료된다고 볼 수는 없고 다른 코루틴을 호출함으로써 일을 잠시 멈춘다고 본다. 그러므로 하나의 코루틴 instance 는 상태값을 유지할 수 있다.
일반적인 서브 루틴은 한 번만 실행되고 종료되지만 하나의 코루틴 instance는 여러번 실행될 수 있다. 함수의 실행되는 지점을 entry point라고 하는데 코루틴은 entry point가 여러개 이다.
아래 queue를 생산하고 소비하는 코루틴 동작을 보자.
var q := new queue coroutine produce loop while q is not full create some new items add the items to q yield to consume coroutine consume loop while q is not empty remove some items from q use the items yield to produce
코루틴은 다른 코루틴을 실행할 수가 있는데, yield 라는 단어를 사용한다. 코루틴은 다른 루틴을 실행할 수 있다고 했다. yield 로 다른 코루틴을 실행하게 되면 다시 이 yield를 실행했던 point 로 돌아올 것이다. 이 관계는 caller-callee 관계가 아닌 동등한(symmetric) 관계로 본다.
이 큐는 완전히 채워지거나 완전히 비워지게 된다. 각 코루틴은 yield 로 서로를 호출한다. yield 가 호출되는 즉시 실행된다.
코루틴은 thread 와 비슷하지만 thread와는 다르다. 코루틴은 light-weight한 Thread 이다. 즉, Thread를 새로 생성하면서 CPU 자원을 사용하지만 코루틴은 호출된 쓰레드에서 주로 사용되어 JVM Heap memory 에서만 object 로 존재한다. 코루틴은 특정 Thread 에서 실행되기 때문에 Concurrency 하게 동작하면서 Parallel하게 동작하듯이 보일 수 있다. 1000개의 쓰레드를 만들 때보다 1000개의 코루틴 instance 를 만드는 것이 훨씬 저렴하다.
https://stackoverflow.com/questions/43021816/difference-between-thread-and-coroutine-in-kotlin
아래 코루틴 예제를 실행해보자.
import kotlinx.coroutines.* println("Start") // Start a coroutine launch { delay(1000) println("Hello") } Thread.sleep(2000) // wait for 2 seconds println("Stop")
Start
Hello
Stop
순으로 호출된다.
launch코루틴 instance 를 만드는 기본적인 function이다.
delayThread.sleep() 과 비슷하지만 실제 thread 를 block 하지는 않는다. 코루틴 instance를 잠시 지연시킬 뿐이다. 코루틴이 잠시 지연될동안, 코루틴을 실행한 thread 는 pool에 들어가있다가 지연이 끝나면 free 한 thread로 다시 실행된다. main thread 는 코루틴의 실행이 끌날동안 반드시 기다려야한다. 그렇지 않으면 Hello는 호출되지 않는다.
main thread의 Thread.sleep() 을 제거하면 Hello는 호출되지 않는다. 이는 main thread 에도 delay() 를 호출하면 해결되는데
Suspend functions are only allowed to be called from a coroutine or another suspend function
와 같은 compile 에러가 날 것이다.
delay는 코루틴 block 에서만 실행되어야하기 때문에 runBlocking {}로 감싸주면 된다.
import kotlinx.coroutines.* println("Start") // Start a coroutine launch { delay(1000) println("Hello") } runBlocking { delay(2000) // wait for 2 seconds } println("Stop")
Thread.sleep(2000)을 제거하여도
Start
Hello
Stop
순으로 호출된다.
Result controlatomic 변수에 sum 하는 코드를 thread 와 코루틴으로 실행해본다.thread atomic sumval startTimeMs = System.currentTimeMillis() val c = AtomicInteger() for (i in 1..1_000_00) thread(start = true) { c.addAndGet(i) } println(c.get()) println("thread : ${System.currentTimeMillis() - startTimeMs} ms")
coroutine atomic sum
val startTimeMs = System.currentTimeMillis() val c = AtomicInteger() for (i in 1..1_000_00) GlobalScope.launch { c.addAndGet(i) } println(c.get()) println("coroutine launch : ${System.currentTimeMillis() - startTimeMs} ms")
705082704 thread : 3529 ms -877182758 coroutine launch : 275 ms
실행시간은 thread 는 몇 분 동안 계산하지만 코루틴은 몇초면 실행이 끝난다.
코루틴쪽 값은 음수가 나오는데 main 의 print 가 실행될 때 몇몇 코루틴 instance 는 실행이 끝나지 않기 때문이다. atomic 변수 말고 다른 변수로 고쳐보자.
Async
async 는 launch 와 비슷하게 코루틴 를 생성하는 코드이다. launch 와 다르게 Deferred<T>라는 키워드로 instance를 return한다. Deferred 는 await() function을 가지고 있다. await() 를 통해 최종 결과를 얻을 수 있다. 또한 Exception이 발생하면 launch는 Application 이 크래시하지만, Deffered는 내부에 Exception이 저장되어 이를 처리하거나 처리하지 않을 수가 있다.
val startTimeMs = System.currentTimeMillis() var deferred: Deferred
var sum = 0 for (i in 1..1_000_00) { deferred = GlobalScope.async { i } runBlocking { sum += deferred.await() } } println(sum) println("coroutine : ${System.currentTimeMillis() - startTimeMs} ms") 705082704 coroutine async : 978 ms
suspend
아래 delay() function을 직접 실행한다고 하면
fun workload(n: Int): Int { delay(1000) return n }
아래와 같은 컴파일 에러가 발생한다.
Suspend functions are only allowed to be called from a coroutine or another suspend function
코루틴의 가장 큰 장점은 thread blocking 없이 코드의 지연을 발생시켜주는 것이다. 컴파일러는 이러한 delay function을 메인 쓰레드에서 호출되는 것을 방지하려는 것이다. 따라서 suspend가 붙은 function은 launch, async, runBlocking 블럭 안에서 실행되어야만 한다.
---- 더보기 ----
runBlocking
runblocking()
delay(), await() 와 같은 지연 function 은 main thread 에서 직접 실행할 수 없다. runBlocking() function 안에서 실행되어야한다. runBlocking() function은 코루틴 function이 complete될 때까지 main thread의 지연을 도와준다.
GlobalScope.launch {
delay(500)
println("Hello from launch")
}
println("Hello from runBlocking after launch")
println("finished runBlocking")
Hello from runBlocking after launch
finished runBlocking
runBlocking {
GlobalScope.launch {
delay(500)
println("Hello from launch")
}
println("Hello from runBlocking after launch")
delay(1000)
println("finished runBlocking")
}
Hello from runBlocking after launch
Hello from launch
finished runBlocking
좀 더 많은 예제
- https://github.com/Kotlin/kotlinx.coroutines
- https://kotlinlang.org/docs/reference/coroutines
- https://kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html
- 쓰레드 지연 이해 - https://medium.com/@elye.project/understanding-suspend-function-of-coroutines-de26b070c5ed
- runBlock 이해 - https://qiita.com/takahirom/items/3e0b7009d2e050e0e56c
반응형'프로그래밍 > Kotlin' 카테고리의 다른 글
Kotlin multiplatform 프로젝트를 생성해보자 (0) 2020.09.01 Lessons learnt using Coroutines Flow in the Android Dev Summit 2019 app (0) 2020.01.29 Improve app performance with Kotlin coroutines (0) 2019.11.29 Kotlin by keyword를 이용해 상속대신 Delegation을 해보자 (0) 2019.09.03 코틀린 - 함수 정의와 호출 (0) 2018.01.03