티스토리 뷰
Chapter 1: 스레드 기반 작업의 한계와 코루틴의 등장
멀티 스레드 프로그래밍
단일 스레드일 경우 작업을 순차적으로 처리해야 하므로 처리 속도, 응답속도 늦어짐
→ 해결 방안으로 멀티 스레드 등장
여러 개의 스레드로 작업을 실행 하여 메인 스레드에 처리가 오래 걸리는 작업이 요청 되었을 때 그 작업을 백그라운드 스레드로 돌려서 처리하도록 함
멀티 스레드 구현 방식
- Thread Class
package chapter1.code5
import kotlin.concurrent.thread
fun main() {
println("[${Thread.currentThread().name}] 메인 스레드 시작")
thread(isDaemon = false) {
println("[${Thread.currentThread().name}] 새로운 스레드 시작")
Thread.sleep(2000L) // 2초 동안 대기
println("[${Thread.currentThread().name}] 새로운 스레드 종료")
}
Thread.sleep(1000L) // 1초 동안 대기
println("[${Thread.currentThread().name}] 메인 스레드 종료")
}
/*
// 결과:
[main] 메인 스레드 시작
[Thread-0] 새로운 스레드 시작
[main] 메인 스레드 종료
[Thread-0] 새로운 스레드 종료
Process finished with exit code 0
*/
- Thread Class를 이용, 각 스레드가 각각 하나의 작업을 실행 가능하므로 메인 스레드 작업과 Thread 스레드가 요청받은 작업은 동시에 실행 가능
- BUT 1) 실행할 때마다 매번 새로운 Thread 인스턴스 생성됨, 2) 스레드 생성과 관리에 대한 책임이 개발자에게 있음
- Executor Framework
package chapter1.code8
import java.util.concurrent.*
fun main() {
val executorService: ExecutorService = Executors.newFixedThreadPool(2)
val future: Future<String> = executorService.submit<String> {
Thread.sleep(2000)
return@submit "작업 1완료"
}
val result = future.get() // 메인 스레드가 블로킹 됨
println(result)
executorService.shutdown()
}
- Executor framework는 Thread Pool 개념을 사용하여 스레드풀을 생성 및 관리하고 사용자로부터 요청받은 작업을 각 스레드에 할당함, 개발자는 오직 ExecutorService에 작업을 제출하기만 하면 됨
- Executor framework 는 작업 대기열과 스레드 풀을 가지고 작업과 스레드 관리
- BUT 메인 스레드가 작업 스레드의 결과를 기다리는(e.g.
get
호출) 동안 thread-blocking됨
//java
Future<Double> futurePrice = getPriceAsync("product");
//다른 스레드가 제품 가격을 계산 하는 동안 .. 이 스레드는 다른 작업 수행
doSomethingElse();
try {
double price = futurePrice.get();//가격 정보가 있으면 Future에서 가격 정보를 읽고
//가격 정보가 없으면 가격 정보를 받을 때까지 block
System.out.println(price);
} catch (Exception e) {
throw new RuntimeException(e);
}
//출처: Modern Java in Action
- 한계 극복 위해 CompletableFuture 등장해서 체이닝, 콜백 등등으로 해결 가능 → BUT 작업 간 종속성이 복잡해질 수록 thread-blocking 피하기 힘듦
코루틴과 스레드 블로킹 문제 해결
코틀린은 작업 단위 코루틴을 통해 스레드 블로킹 문제 해결
어떻게 해결? 다음과 같은 방식으로 해결함
- coroutine ==
suspendable computation
임. - 즉 코루틴은 작업이 중단되어 스레드 사용이 필요하지 않으면, 스레드에서 제거되며 다른 코루틴을 위해 스레드를 양보할 수 있음. 따라서 thread-blocking이 일어나지 않음.
computation이 완료되면, 다시 재개되어 스레드에 할당되어 실행됨
import kotlinx.coroutines.*
fun main() = runBlocking {
repeat(50_000) { // launch a lot of coroutines
launch {
delay(5000L)
print(".")
}
}
}
- 코루틴은 코루틴은 JVM thread 보다
less resource-intensive
이며,lightweight thread
라고 불림 → 스레드에서 작업 실행하는 것과 유사 & 스레드에서 블로킹되던 것이 코루틴을 일시 중단하는 것으로 바뀐 것 Structured Concurrency(구조화된 동시성)
을 통해 비동기 작업을 안전하게 & 예외 처리를 효과적으로 처리
Chapter2: 코루틴 기본 실습
My First Coroutine
package org.example
import kotlinx.coroutines.*
fun main() = runBlocking<Unit>(context = CoroutineName("Main")){
launch(context = CoroutineName("Coroutine1")) { // launch a new coroutine and continue
delay(1000L) // non-blocking delay for 1 second
println("[${Thread.currentThread().name}] Kotlin!") // print after delay
}
launch(context = CoroutineName("Coroutine2")) { // launch a new coroutine and continue
delay(2000L) // non-blocking delay for 2 second
println("[${Thread.currentThread().name}] World!") // print after delay
}
println("[${Thread.currentThread().name}] Hello") // main coroutine continues while a previous one is delayed
}
//
//[main @Main#1] Hello
//[main @Coroutine1#2] Kotlin!
//[main @Coroutine2#3] World!
3개의 코루틴 생성: runBlocking, launch1, launch2
launch(context=CoroutineName("")
: 코루틴 이름 지정-DKotlinx.coroutines.debug
추가 시 스레드 이름 출력할 때 코루틴 이름 출력 가능launch: coroutine builder, 새로운 코루틴을 launch 한다 (⇒ 독립적으로 동작)
delay: suspending function, 특정 시간동안 코루틴을 suspend 한다, suspending 은 스레드를 block 하지 않으며, 다른 코루틴이 그 스레드를 이용해 실행되게끔 한다
runBlocking: coroutine builder, non-coroutine-world 와 coroutine 을 연결,
runBlocking { .. }
안에 있는 모든 코루틴이 실행을 완료될 때까지 해당 스레드가 block 됨
Suspending Function
import kotlinx.coroutines.*
fun main() = runBlocking { // this: CoroutineScope
launch { doWorld() }
println("Hello")
}
// this is your first suspending function
suspend fun doWorld() {
delay(1000L)
println("World!")
}
- suspending function: 코루틴 안에서 regular function 처럼 사용될 수 있고, suspending function 안에서 다른 suspending function (e.g. delay) 을 사용할 수 있음
Scope Builder
import kotlinx.coroutines.*
fun main() = runBlocking {
doWorld()
}
suspend fun doWorld() = coroutineScope { // this: CoroutineScope
launch {
delay(1000L)
println("World!")
}
println("Hello")
}
//Hello
//World!
- coroutineScope 를 이용해 scope 선언 가능 → lauched 된 코루틴이 끝날 때까지 종료되지 않음
- runBlocking, coroutiineScope 빌더 차이? : runBlocking 은 현재 스레드를 block, coroutineScope 는 일시 중지(suspend) 하고 현재 스레드를 다른 용도로 release 함, runBlocking 은 regular function 이고 coroutineScope는 suspending function 임
'공부 > Kotlin' 카테고리의 다른 글
코루틴 컨텍스트 (Coroutine Context) (0) | 2024.06.21 |
---|---|
코루틴 디스패처(Coroutine Dispatcher) (0) | 2024.06.20 |
코루틴 빌더 기초 (0) | 2024.06.14 |