일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- MiTweet
- Compose
- MyVoca
- activity
- Kotlin
- boj
- Python
- androidStudio
- GitHub
- 암호학
- Rxjava
- AWS
- 프로그래머스
- Coroutine
- Codeforces
- livedata
- Gradle
- ProGuard
- Hilt
- 백준
- relay
- 코루틴
- architecture
- TEST
- 코드포스
- pandas
- Coroutines
- textfield
- 쿠링
- android
- Today
- Total
이동식 저장소
[Kotlin] Coroutines - Basics 본문
코루틴
코루틴이란 비동기적으로 실행되는 코드를 간단하게 작성하기 위해 사용할 수 있는 설계 패턴이다.
- 코루틴은 실행 중인 스레드를 중단시키지 않는
suspend
를 지원하기 때문에 하나의 스레드에서 여러 개의 코루틴을 실행할 수 있다. 코루틴은 스택조차 없기 때문에 스레드보다도 메모리를 더 절약할 수 있다. - 코루틴은 작업 범위를 설정하여 그 안에서 비동기 작업을 구조화한다. 글을 읽다 보면 무슨 말인지 알게 될 것이다.
- 코루틴은 부모-자식 간의 관계를 설정하여 자식의 취소(코드 중단)를 부모에게 전달할 수 있다.
- (안드로이드 한정) 많은 Jetpack 라이브러리에서 코루틴을 완벽히 지원하고 있다.
시작하기 전에
사실 코루틴은 코틀린의 기본 패키지에 포함되어 있지 않다. 플러그인을 로드해야 코루틴을 제대로 사용할 수 있다. IntelliJ IDEA 기준으로 Settings-Project Structure-Libraries
에서 Maven
을 선택하자. Android Studio에서도 동일하다.
kotlinx-coroutines-core
로 검색하여 최신 stable 버전을 설치하자. Android Studio
에서 사용하려면 core
말고 kotlinx-coroutines-android
를 설치해야 한다.
정상적으로 설치했으면 IntelliJ에서 kotlinx-coroutines-core
패키지를 확인할 수 있다.
Basics
다음의 코드를 무작정 실행해 보자.
fun main() {
GlobalScope.launch {
delay(1000L)
println("World!")
}
println("Hello,")
Thread.sleep(2000L)
}
Hello,
World!
Hello,
가 출력되고 약 1초 후에 World!
가 출력된다. 위의 코드에서는 launch
코루틴 빌더에 의해 코루틴이 실행되었다. GlobalScope
에서 코루틴이 실행되었는데, GlobalScope
안의 코루틴은 전체 프로그램과 생명 주기가 같다. 즉 프로그램이 종료되지 않는 한 계속해서 실행될 수 있다.
물론 GlobalScope
를 잘 이용하면 daemon 스레드와 비슷한 효과를 낼 수 있다.
blocking vs. non-blocking
위에서 delay()
와 Thread.sleep()
을 사용하였다. 초보 프로그래머라도 이 함수의 의미를 쉽게 알 수 있을 것이다. 두 함수는 모두 일정 시간(ms) 동안 프로그램의 작동을 멈추지만, 각각 _non-blocking_과 _blocking_이라는 큰 차이점이 있다.
- _non-blocking_이란 호출된 함수(callee)가 _어떤 값_을 즉시 반환하여 caller에게 자신의 작업을 계속 할 수 있도록 한다. 물론 callee의 실행 결과까지 즉시 반환되지는 않을 수 있으므로 진짜 결과값은 나중에 받아야 한다.
launch
함수가 _non-blocking_이다. - _blocking_이란 callee가 실행될 때까지 caller를 기다리게 하는 것을 의미한다. 보통 우리가 작성하는 함수는 _blocking_이다.
참고: Blocking-NonBlocking, Synchronous/Asynchronous
blocking 함수 Thread.sleep()
대신 non-blocking 함수만 사용해 보자.
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch {
delay(1000L)
println("World!")
}
println("Hello,")
runBlocking {
delay(2000L)
}
}
Hello,
World!
출력 결과는 같지만 사용한 함수가 다름을 확인할 수 있다. runBlocking
은 블럭 안의 코드가 모두 실행될 때까지 caller의 스레드를 _block_한다.
그런데 위의 코드는 코루틴이 마구 얽혀 있어 보기에 좋지 않다. 사실 다음과 같이 main
함수 전체를 runBlocking
으로 묶을 수도 있다.
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> {
GlobalScope.launch {
delay(1000L)
println("World!")
}
println("Hello,")
delay(2000L)
}
runBlocking<Unit> {...}
을 사용하여 top-level 코루틴을 작성하였다. 브래킷 안의 Unit
은 반환 값을 의미한다. 코틀린에서 main
함수는 Unit
을 반환하기 때문이다.
같은 방법을 사용하여 suspending 함수를 테스트하는 코드를 작성할 수 있다. suspending 함수가 무엇인지는 나중에 알아볼 것이다.
class MyTest {
@Test
fun testMySuspendingFunction() = runBlocking<Unit> {
// 여기서 suspending 함수를 테스트
}
}
작업이 완료되었습니다
사실 위에서처럼 일정 시간 동안 delay
하는 코드는 별로 좋지 않다. 작업이 언제 끝날 줄 알고?
일정 시간 동안 기다리는 대신, 작업이 완료될 때까지 기다리는 것이 더 안전하다. non-blocking 방식으로 코루틴이 종료될 때까지 기다려 보자.
val job = GlobalScope.launch { // launch는 Job 객체를 반환한다.
delay(1000L)
println("World!")
}
println("Hello,")
job.join() // job이 완료될 때까지 기다린다.
Hello,
World!
여전히 결과는 같지만, 로직 자체가 완전히 달라졌음을 알 수 있다. launch
함수가 반환하는 Job 객체를 사용하여 해당 코드가 완료될 때까지 기다렸다.
Structured concurrency
GlobalScope.launch
를 사용하면 최상위 코루틴을 만들 수 있다. 코루틴 역시 코드이기 때문에 (적은 양이지만) 프로그램이 실행되는 동안 메모리를 차지한다. GlobalScope
코루틴은 프로그램이 종료될 때까지 실행될 수 있기 때문에, 철저히 관리하지 않으면 메모리 누수가 발생하기 쉽다. 따라서 GlobalScope
대신 structured concurrency 개념을 적용하여 코루틴의 실행 범위를 지정하는 것이 좋다.
다음의 코드를 보자.
fun main() = runBlocking { // this: CoroutineScope
launch { // runBlocking의 제어 안에서 launch 실행
delay(1000L)
println("World!")
}
println("Hello,")
}
runBlocking
을 사용하여 최상위 코루틴을 작성하였다. runBlocking
을 포함한 모든 코루틴 빌더는 CoroutineScope 객체를 블럭 안에서 참조할 수 있다. 이 안에서 실행된 launch
는 runBlocking
의 자식 코루틴이 되고, runBlocking
은 다음 줄을 실행하기 전에 자식(여기서는 launch
)이 종료될 때까지 기다린다.
Hello,
World!
Scope builder
물론 자동으로 제공되는 CoroutineScope 대신 coroutineScope
함수를 이용하여 직접 범위를 지정할 수 있다. coroutineScope
함수를 이용하여 만든 코루틴 스코프는 모든 자식이 종료될 때까지 기다린다.
위에서 살펴본 runBlocking
역시 자식이 종료될 때까지 기다렸다. 하지만 runBlocking
은 기다리는 동안 스레드를 _block_하며, coroutineScope
는스레드를 _suspend_하기 때문에 스레드가 다른 작업을 실행할 수 있다. _block_과 _suspend_를 잘 구분하자.
- blocking: 코드가 실행될 때까지 스레드를 잡아둔다. 따라서 해당 스레드는 기다리는 것 외의 다른 작업을 할 수 없다.
- suspend: 코드가 실행되는 동안 스레드는 다른 작업을 수행할 수 있다.
다음의 코드를 직접 실행하여 이해해 보자.
fun main() = runBlocking { // this: CoroutineScope
launch {
delay(200L)
println("Task from runBlocking") // 2
}
coroutineScope { // Creates a coroutine scope
launch {
delay(500L)
println("Task from nested launch") // 3
}
delay(100L)
println("Task from coroutine scope") // 1
}
println("Coroutine scope is over") // 4
}
Task from coroutine scope
Task from runBlocking
Task from nested launch
Coroutine scope is over
coroutineScope
가 완료되지 않았지만("Task from nested launch"가 아직 출력되지 않았지만) launch
의 "Task from runBlocking"이 출력되었음을 알 수 있다. 즉 coroutineScope
내부의 launch
를 기다리는 동안 외부의 launch
가 실행될 수 있는 것이다.
물론 최상위 runBlocking
은 내부의 모든 코루틴이 종료될 때까지 기다려야 하지만, coroutineScope
을 기다리는 동안 다른 작업(외부의 launch
)를 수행할 수 있다는 말이다.
suspend 함수
맨 처음의 "Hello World" 코드에서 launch
부분을 별도의 함수로 떼어내 보자.
fun main() = runBlocking {
launch { doWorld() }
println("Hello,")
}
fun doWorld() {
delay(1000L)
println("World!")
}
그런데 코드를 보면 다음과 같은 에러가 발생한다.
Suspend function 'delay' should be called only from a coroutine or another suspend function
doWorld
는 코루틴에서 실행되고 있긴 하지만 코루틴 자체는 아니므로, doWorld
를 suspend
함수로 만들어야 한다.
fun main() = runBlocking {
launch { doWorld() }
println("Hello,")
}
// suspending function
suspend fun doWorld() {
delay(1000L)
println("World!")
}
suspend 함수 내부에서는 delay
등의 다른 suspend 함수를 사용할 수 있다. 즉 suspend 함수를 호출하는 함수 역시 _suspend_여야 한다는 말이다.
코루틴은 정말 가볍습니다
다음의 코드를 실행해 보자.
fun main() = runBlocking {
repeat(100_000) { // launch a lot of coroutines
launch {
delay(5000L)
print(".")
}
}
}
이 코드는 10만 개의 코루틴을 만든다. 코루틴 대신 스레드를 사용하면 아마도 OutOfMemoryError
류의 에러가 발생할 것이다.
참고 문헌
'Primary > Kotlin' 카테고리의 다른 글
[Kotlin] Coroutines - Suspending 함수 활용하기 (0) | 2021.01.20 |
---|---|
[Kotlin] Coroutines - Cancellation and Timeouts (0) | 2021.01.20 |
[Kotlin] Sequence (0) | 2021.01.15 |
[Kotlin] Thread 생성 및 실행 (0) | 2021.01.14 |
[Kotlin] Collections 확장 함수 (0) | 2021.01.13 |