전전 공댕이의 공부 기록

[코틀린 쿡북 13장.1] 코루틴과 구조적 동시성 본문

Kotlin/기본 개념

[코틀린 쿡북 13장.1] 코루틴과 구조적 동시성

Ashton 2021. 4. 29. 18:40

레시피 13.1 내용만 작성했습니다.

 

코루틴이란?

개발자가 동시성 코드를 synchronous 코드처럼 작성할 수 있게 해줌

다른 방법들보다 훨씬 쉽게 동시적 코드를 작성할 수 있다 (동시적 코드 작성이 쉽다는 것 (X) 쉬워진다는 것 (O))

 

코루틴을 일시정지하고 재개할 수 있음

suspend 키워드와 함께 함수를 생성 -> 복잡한 멀티스레딩 코드를 직접 작성하지 않고도 함수를 임시로 정지하고 나중에 다른 스레드에서 이 정지된 함수를 다시 재개할 수 있음을 시스템에게 알려줌

 

13장에서 다룰 것들

코틀린 코루틴과 연관된 주제

- 코루틴 영역 (scope)

- 코루틴 컨텍스트 사용하기

- 적절한 코루틴 빌더 & 디스패치 선택하기

- 코루틴 동작 조정하기

 

 

참고 사이트: wooooooak.github.io/kotlin/2019/08/25/%EC%BD%94%ED%8B%80%EB%A6%B0-%EC%BD%94%EB%A3%A8%ED%8B%B4-%EA%B0%9C%EB%85%90-%EC%9D%B5%ED%9E%88%EA%B8%B0/


[레시피 13.1] 코루틴 빌더 선택하기

문제

코루틴을 생성하는 올바른 함수를 선택해야 한다.

 

해법

사용 가능한 빌더 함수 중에 선택한다.


코루틴 빌더 함수

새 코루틴을 생성하기 위해 빌더 함수 3가지 중 하나를 사용할 수 있다.

1. runBlocking

2. launch

3. async

 

- runBlocking: 최상위 함수

- launch & async: CoroutineScope의 확장 함수

 

주의 사항: GlobalScope의 launch와 async 

GlobalScope에 정의된 launch와 async 버전도 있고, 이들이 완전하게 제거될 것이 아니라면 이 두 가지 함수를 사용하지 않는 것을 권장한다. 

GlobalScope의 launch와 async의 문제점: 시작하는 코루틴이 특정 코루틴 잡에도 할당되지 않고 영구적으로 취소되지 않으면 애플리케이션의 전체 수명주기에 걸쳐 실행된다.

 

1. runBlocking 빌더

- 명령줄 검증 / 테스트에 유용

- 현재 스레드를 블록하고 모든 내부 코루틴이 종료될 때까지 블록함

 

시그니처

fun <T> runBlocking(block: suspend CoroutineScope.() -> T): T

runBlocking 함수는 suspend 함수가 아니므로 보통 함수에서 호출할 수 있습니다. 

인자로 CoroutineScope에 확장함수로 추가될 suspend함수를 받고, 인자로 받은 이 함수를 실행하고, 실행한 함수가 리턴하는 값을 리턴합니다.

 

예제

import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking

fun main() {
   println("Before creating coroutine")
   runBlocking {
       print("Hello, ")
       delay(200L)
       println("World!")
   }
   println("After coroutine is finished")
}

//Before creating coroutine
//Hello, World!
//After coroutine finished

여기서, "Hello, " 와 "World!" 사이에 200ms의 지연이 있습니다!

 

2. launch 빌더

- 독립된 프로세스를 실행하는 코루틴을 시작하고, 해당 코루틴에서 리턴값을 받을 필요가 없다면 사용

- CoroutineScope의 확장 함수 (-> CoroutineScope이 사용 가능한 경우에만 사용 가능)

 

시그니처

fun <T> CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job

-> launch 함수는 코루틴 취소가 필요하면 사용할 수 있는 Job 인스턴스를 리턴합니다.

-> CoroutineContext는 다른 코루틴과 상태를 공유하기 위해 사용합니다.

-> CoroutineStart 파라미터는 오직 4가지 값만 가질 수 있는 열거형 클래스입니다. : DEFAULT, LAZY, ATOMIC, UNDISPATCHED (여기서는 DEFAULT를 가졌네요!)

-> 마지막 파라미터로 제공된 람다는 반드시 인자가 없는 일시 중단 함수이고, 아무것도 리턴하지 않아야 합니다.

 

예제

fun main() {
    println("Before runBlocking")
    ruBlocking{
        println("Before launch")
        launch{
            print("Hello, ")
            delay(200L)
            println("World!")
        }
        println("After launch")
    }
    println("After runBlocking")
}

//Before runBlocking
//Before launch
//After launch
//Hello, World!
//After runBlocking

여기서도 200ms 지연이 있습니다!

 

3. async 빌더

- 값을 리턴해야하는 경우 사용

- CoroutineScope의 확장 함수

 

시그니처

fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T>

 

Deferred

직역: 연기하다, 연기된

 

launch 빌더와 거의 비슷한데, 3가지 다른 부분이 있습니다.

그 중 가장 큰 차이가 Deferred로 감싸진다는 점인데, 파라미터로서 제공한 일시 중단 함수는 값을 리턴하면 async 함수가 지연된(deferred) 인스턴스로 해당 값을 감쌉니다. Deferred에서 알아야 할 중요한 함수가 생산된 값을 리턴하기 전에 코루틴이 완료될 때까지 기다리는 await입니다.

 

예제

//async로 코루틴 생성
import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlin.random.Random

suspend fun add(x:Int, y:Int): Int {
    delay(Random.nextLong(1000L))
    return x+y
}

suspend fun main() = coroutineScope {
    val firstSum = async {
        println(Thread.currentThread().name)
        add(2,2)
    }
    val secondSum = async {
        println(Thread.currentThread().name)
        add(3,4)
    }
    println("Awaiting concurrent sums...")
    val total = firstSum.await() + secondSum.await()
    println("Total is $total")
}

 

+coroutineScope 빌더

- 종료 전에 포함된 모든 코루틴이 완료될 때까지 기다리는 일시 중단 함수

 

장점

1) 메인 스레드를 블록하지 않음

2) 코루틴 완료 여부를 확인하기 위해 코루틴을 조사할 필요가 없음

(코루틴 사용 기본 원칙: 정의된 영역 안에 코루틴을 사용해야 한다)

    -> 자동으로 모든 자식 코루틴이 완료될 때까지 기다림

 

- 다만, 반드시 일시 중단 함수의 일부로서 호출돼야 함

 

시그니처

suspend fun <R> coroutineScope(
    block: suspend CoroutineScope.() -> R
): R

 

예제

 

-> 10개의 코투린을 시작하고 각 코루틴은 자신 이전에 실행된 코루틴보다 10ms 적게 지연됩니다. 즉 화면에 출력된 결과에는 :)이 포함되어있고 숫자는 내림차순입니다.

-> 코루틴의 일반적인 패턴

coroutineScope로 시작해서 코루틴이 모두 포함된 영역을 설정하고 결과 블록 안에서 개별 작업을 다루기 위해 launch 또는 async를 사용하기도 합니다.

이후 이 영역은 프로그램 종료 전 모든 코루틴이 완료될 때까지 기다리고, 만약 코루틴이 하나라도 실패하면 나머지 코루틴을 취소합니다.

이 방식은 루틴의 완료 여부를 조사 하지 않고도 균형 있는 제어와 에러 처리를 달성하고 루틴이 실패하는 경우를 처리하지 않는 것을 방지합니다.

하나의 코루틴이 실패하면 모든 코루틴이 취소될 수 있게 coroutineScope 내부에서 모든 코루틴을 실행시키는 관습은 구조화된 동시성으로 알려져있다.

 

동적인 부분이 많고, 가능한 조합이 너무 많아 어려울 수 있지만, 다행히 주로 소수의 조합만이 사용됩니다!

 

'Kotlin > 기본 개념' 카테고리의 다른 글

[코틀린 쿡북 12장] 스프링 프레임워크  (0) 2021.04.29