일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- 그리디
- swea
- Kotlin
- dfs
- 알고리즘
- 이분탐색
- 플로이드-와샬
- BFS
- JUnit 5
- 스택
- java
- 후니의 쉽게 쓴 시스코 네트워킹
- 동적계획법
- 프로그래머스
- 구현
- 세그먼트 트리
- Effective Java
- 유니온 파인드
- mst
- Network
- 투 포인터
- 문자열
- 백준
- 위상정렬
- 백트래킹
- 에라토스테네스의 체
- CS
- 시뮬레이션
- 완전탐색
- 수학
반갑습니다!
[Kotlin] Coroutine Basics 본문
Your first coroutine
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch { // 백그라운드에서 새로운 코루틴 실행
delay(1000L) // 1초간 논블로킹 딜레이
println("World!")
}
println("Hello,") // 코틀린이 지연되는 동안 메인 쓰레드 실행
Thread.sleep(2000L) // 2초 동안 프로그램이 살아있음
}
/* 실행결과
Hello,
World!
*/
위 코드를 실행해보면 쓰레드를 실행한 것처럼 동작함을 알 수 있다. 여기서 GlobalScope
로 생성된 블럭을 'Coroutine Scope' 라고 한다. 그리고 launch
를 통해 코루틴을 실행시키는데, 이러한 역할을 하는 것을 'Coroutine Builder'라고 한다.
GlobalScope
는 이름 그대로 전역 범위를 갖기 때문에 프로그램과 생명주기를 같이한다.
위의 예제에서는 delay()
와 Thread.sleep()
을 함께 사용하였다. 하지만 이런식으로 코드를 작성하다보면 실행 흐름을 파악하기가 어려워진다는 단점이 있다. 그렇다고 해서 Thread.sleep()
을 delay()
로 대체하게되면 컴파일 에러가 발생하는데, 이는 delay()
는 코루틴을 중단시키는 목적으로 구현된 suspend
함수이기 때문이다. 즉, suspend
함수는 코루틴 스코프 안에서만 사용할 수 있다. 이를 개선하기 위해 또 다른 코틀린 빌더인 runBlocking
을 사용하였다.
Bridging blocking and non-blocking worlds
import kotlinx.coroutines.*
fun main() {
GlobalScope.launch { // 백그라운드에서 새로운 코루틴 실행
delay(1000L)
println("World!")
}
println("Hello,") // 메인 쓰레드 코루틴은 즉시 실행
runBlocking { // runBlocking이 메인 쓰레드를 블로킹
delay(2000L) // 2초 동안 프로그램이 살아있음
}
}
/* 실행결과
Hello,
World!
*/
처음 코드와 결과는 동일하지만 논블로킹 함수인 delay)()
만 사용하였다. runBlocking
을 호출하는 메인 쓰레드는 runBlocking
이 완료될 때까지 블록된다. 하지만 이러한 코드는 관용적인 코드가 아니다. 이를 조금 수정하면 다음과 같다.
import kotlinx.coroutines.*
fun main() = runBlocking<Unit> { // 메인 코루틴 시작
GlobalScope.launch {
delay(1000L)
println("World!")
}
println("Hello,")
delay(2000L)
}
/* 실행결과
Hello,
World!
*/
하지만 다른 코루틴이 끝날 때까지 임의의 시간동안 기다리는 것은 좋은 방법이 아니다. 다른 코루틴이 언제 끝날지 알 수 없는 경우 정상적인 작동을 보장할 수 없다는 문제가 있기 때문이다. 이번에는 임의의 시간이 아니라 코루틴이 끝날 때까지 기다려보자.
Waiting for a job
import kotlinx.coroutines.*
fun main() = runBlocking {
val job = GlobalScope.launch { // Job객체에 대한 참조를 유지하면서 코루틴 실행
delay(1000L)
println("World!")
}
println("Hello,")
job.join() // 자식 코루틴이 끝날 때까지 기다린다.
}
/* 실행결과
Hello,
World!
*/
launch
를 하게되면 Job객체를 반환하고 실행된다. 따라서 코루틴이 실행되는 동안에도 Job 객체에 대한 참조를 유지할 수 있다. 그리고 join()
을 사용하면 해당 Job 객체의 작업이 끝날 때가지 기다릴 수 있다.
Structured concurrency
이번엔 코루틴을 여러개 생성해보자.
import kotlinx.coroutines.*
fun main() = runBlocking {
val job1 = GlobalScope.launch { // Job객체에 대한 참조를 유지하면서 코루틴 실행
delay(1000L)
println("World!")
}
val job2 = GlobalScope.launch { // Job객체에 대한 참조를 유지하면서 코루틴 실행
delay(1000L)
println("World!")
}
val job3 = GlobalScope.launch { // Job객체에 대한 참조를 유지하면서 코루틴 실행
delay(1000L)
println("World!")
}
val job4 = GlobalScope.launch { // Job객체에 대한 참조를 유지하면서 코루틴 실행
delay(1000L)
println("World!")
}
val job5 = GlobalScope.launch { // Job객체에 대한 참조를 유지하면서 코루틴 실행
delay(1000L)
println("World!")
}
println("Hello,")
job1.join()
job2.join()
job3.join()
job4.join()
job5.join()
}
/* 실행결과
Hello,
World!
World!
World!
World!
World!
*/
이런식으로 코루틴이 늘어나면 늘어날수록 join()
함수를 계속해서 생성해야하기 때문에 좋지 않다. 이는 메인 코루틴과 GlobalScope
로 선언된 코루틴 사이의 구조적인 관계가 없기 때문이다. 이는 코루틴들 간에 구조적인 관계를 만들어줌으로써 개선할 수 있다.
import kotlinx.coroutines.*
fun main() = runBlocking { // 코루틴 스코프
launch {
delay(1000L)
println("World!")
}
launch {
delay(1000L)
println("World!")
}
launch {
delay(1000L)
println("World!")
}
launch {
delay(1000L)
println("World!")
}
launch {
delay(1000L)
println("World!")
}
println("Hello,")
}
/* 실행결과
Hello,
World!
World!
World!
World!
World!
*/
메인 함수는 runBlocking
코루틴 빌더를 통해 코루틴에서 실행되고, 그 안에서 5개의 코루틴이 생성되었다. runblocking으로 생성된 코루틴은 자식 코루틴이 종료될 때까지 종료되지 않기 때문에 위의 예제는 정상적으로 작동한다.
Extract function refactoring
이번엔 "World!" 를 출력하는 부분을 별도의 함수로 분리해보자. 위에서 설명했듯이 delay()
는 코루틴 스코프에서만 사용할 수 있기 때문에 suspend
키워드를 사용해서 함수를 작성해야 한다.
import kotlinx.coroutines.*
fun main() = runBlocking {
launch { doWorld() }
println("Hello,")
}
suspend fun doWorld() {
delay(1000L)
println("World!")
}
/* 실행결과
Hello,
World!
*/
Coroutines ARE light-weight
코루틴은 쓰레드와 비슷하게 동작하지만 쓰레드보다 가볍다는 특징이 있다. 다음 예제를 실행해보며 직접 확인해보자.
import kotlinx.coroutines.*
fun main() = runBlocking {
repeat(100_000) { // 100000개의 코루틴 생성
launch {
delay(1000L)
print(".")
}
}
}
온점 10만개가 무리 없이 출력된다. 이번엔 동일한 코드를 쓰레드로 변경해보자.
import kotlinx.coroutines.*
import kotlinx.concurrent.thread
fun main() = runBlocking {
repeat(100_000) {
thread {
Thread.sleep(1000L)
println(".")
}
}
}
실행해보면 코루틴이 쓰레드보다 가볍다는 것을 알 수 있을 것이다.
Global coroutines are like daemon threads
import kotlinx.coroutines.*
fun main() = runBlocking {
GlobalScope.launch {
repeat(1000) { i ->
println("I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // just quit after delay
}
/* 실행결과
I'm sleeping 0 ...
I'm sleeping 1 ...
I'm sleeping 2 ...
*/
GlobalScope
는 어플리케이션과 수명을 같이하기 때문에 어플리케이션이 종료되면 코루틴도 같이 종료된다.
'Kotlin' 카테고리의 다른 글
[Kotlin] Composing Suspending Functions (0) | 2020.07.05 |
---|---|
[Kotlin] Cancellation and Timeouts (0) | 2020.07.04 |
[Kotlin] Coroutine Guide (0) | 2020.07.04 |
[Kotlin] 반복문 (0) | 2020.06.15 |
[Kotlin] 조건문 (0) | 2020.06.15 |