티스토리 뷰

공부/Kotlin

Kotlin 코루틴의 기본

흑개1 2024. 6. 9. 22:27

Chapter 1: 스레드 기반 작업의 한계와 코루틴의 등장

멀티 스레드 프로그래밍

단일 스레드일 경우 작업을 순차적으로 처리해야 하므로 처리 속도, 응답속도 늦어짐

→ 해결 방안으로 멀티 스레드 등장

여러 개의 스레드로 작업을 실행 하여 메인 스레드에 처리가 오래 걸리는 작업이 요청 되었을 때 그 작업을 백그라운드 스레드로 돌려서 처리하도록 함

멀티 스레드 구현 방식

  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) 스레드 생성과 관리에 대한 책임이 개발자에게 있음
  1. 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 피하기 힘듦

코루틴과 스레드 블로킹 문제 해결

코틀린은 작업 단위 코루틴을 통해 스레드 블로킹 문제 해결

어떻게 해결? 다음과 같은 방식으로 해결함

Untitled

  • 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
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/11   »
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
글 보관함