Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
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
Tags
more
Archives
Today
Total
관리 메뉴

훌륭한 개발자가 되기 위하여

구조화된 동시성 본문

안드로이드

구조화된 동시성

jay20033 2025. 1. 11. 16:48

구조화된 동시성

구조화된 동시성 원칙 : 비동기 작업을 구조화해 비동기 프로그래밍을 보다 안정적이고, 예측 가능하게 만드는 원칙

  • 코루틴을 부모-자식 관계로 구조화해 코루틴이 보다 안전하게 관리되고 제어될 수 있다
  • 코루틴 빌더 함수의 람다식 안에서 새로운 코루틴 빌더 함수를 호출하면 된다.
fun main() = runBlocking<Unit> {
    launch {
        println("부모 코루틴 실행")
        launch {
            println("자식 코루틴 실행")
        }
    }
}

실행 환경 상속

  • 부모 코루틴은 자식 코루틴에게 실행 환경을 상속한다.
  • 부모 코루틴이 자식 코루틴을 생성하면 부모 코루틴의 CoroutineContext가 자식 코루틴에게 전달된다.
  • 자식 코루틴을 생성하는 코루틴 빌더 함수에 새로운 CoroutineContext 객체가 전달되면, 부모 코루틴에서 잔달받은 CoroutineContext 구성 요소들은 자식 코루틴 빌더 함수로 전달된 CoroutineContext 객체의 구성 요소들로 덮어 씌워진다.

상속되지 않는 Job

  • launch나 async 함수는 호출 시마다 코루틴 추상체인 Job객체를 생성하는데 코루틴 제어에 Job객체가 필요하기 때문에 Job 객체를 부모 코루틴으로부터 상속받지 않는다.
  • 즉, 코루틴 빌더를 통해 생성된 코루틴들은 서로 다른 Job을 가진다.
  • 부모 코루틴으로부터 전달 받은 Job 객체는 코루틴을 구조화 하는데 사용된다.
  • 코루틴 빌더가 호출되면 Job 객체는 새롭게 생성되지만, 생성된 Job 객체는 내부에 정의된 parent 프로퍼티를 통해 부모 코루틴의 Job 객체에 대한 참조를 가진다.
  • 부모 코루틴의 Job 객체는 Sequence 타입의 children 프로퍼티를 통해서 자식 코루틴들의 Job에 대한 참조를 가진다.

Job의 프로퍼티 타입 설명
parent Job? 코루틴은 부모 코루틴이 없을 수 있고, 부모 코루틴이 있더라도 최대 하나이다.
children Sequence<Job> 하나의 코루틴이 복수의 자식 코루틴을 가질 수 있다.

구조화된 코루틴의 특성

1. 취소의 전파

  • 코루틴으로 취소가 요청되면 자식 코루틴에 취소가 전파된다.

2. 부모 코루틴의 자식 코루틴에 대한 완료 의존성

  • 부모 코루틴은 모든 자식 코루틴이 실행 완료되야 완료될 수 있다.
  • 코루틴의 구조화는 큰 작업을 연관된 여러 작은 작업으로 나누는 방식으로 일어나는데, 작은 작업이 완료되야 큰 작업이 완료될 수 있기 때문이다.

CoroutineScope 사용해 코루틴 관리하기

  • CoroutineScope는 내부에 코루틴 실행 환경인 CoroutineContext를 가진 단순한 인터페이스
  • CoroutineScope 생성 함수를 통해 CoroutineScope 객체를 생성할 수 있다
public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
    ContextScope(if (context[Job] != null) context else context + Job())

fun main() {
    val coroutineScope = CoroutineScope(Dispatchers.IO)
    coroutineScope.launch {
        delay(100L)
        println("[${Thread.currentThread().name}] 코루틴 실행 완료")
    }
    Thread.sleep(1000L)
}
// 실행 결과
[DefaultDispatcher-worker-1 @coroutine#1] 코루틴 실행 완료

CoroutineScope 객체가 코루틴에게 실행 환경을 제공하는 방식

👉 launch, async 함수는 다음 과정을 통해 CoroutineScope 객체로부터 실행 환경을 제공 받아 실행 환경을 설정한다.

  1. 수신 객체인 CoroutineScope으로부터 CoroutineContext 객체를 제공 받는다.
  2. 제공 받은 CoroutineContext 객체에 launch 함수의 context 인자로 넘어온 CoroutineContext를 더한다.
  3. 생성된 CoroutineContext에 새로운 Job 객체를 생성해 더한다.

        👉 이 때 CoroutineContext를 통해 전달되는 Job객체는 새로 생성된 Job 객체의 부모 Job이 된다.

CoroutineScope에 속한 코루틴의 범위

  • CoroutineScope 객체는 특정 범위의 코루틴을 제어하는 역할을 한다.
  • 범위는 CoroutineScope의 Job에 해당하는 하위의 모든 코루틴이다.
  • 범위를 따로 지정하고 싶으면 임의의 CoroutineScope를 생성하면 된다.

CoroutineScope 취소하기

  • CoroutineScope 인터페이스는 확장함수로 cancel 함수를 가지고 cancel 함수는 CoroutineScope 범위에 속한 모든 코루틴을 취소한다.
  • 이 때, 취소를 위해 CoroutineScope의 coroutineContext의 Job을 취소하는 방법을 사용한다. ✔ 취소의 전파 특성 사용

코루틴의 구조화와 Job

  • runBlocking 함수를 호출하면 부모 Job이 없는 루트 Job 객체가 생성된다.
    • 루트 Job : 부모 Job 객체가 없는 구조화의 시작점 역할을 하는 Job 객체
    • 루트 코루틴 : 이 Job 객체에 의해 제어되는 코루틴

Job 구조화 깨기

1. CoroutineScope 사용

  • CoroutineScope 생성 함수는 새로운 루트 Job을 가진 CoroutineContext를 생성한다.

2. Job 객체 생성해 구조화 깨기

  • Job 생성 함수로 새로운 루트 Job을 생성할 수 있다.
fun main() = runBlocking<Unit> {
    val newRootJob = Job()
    launch(CoroutineName("Coroutine1") + newRootJob) {
        launch(CoroutineName("Coroutine3")) {
            delay(100L)
            println("[${Thread.currentThread().name}] 코루틴 실행")
        }
        launch(CoroutineName("Coroutine4")) {
            delay(100L)
            println("[${Thread.currentThread().name}] 코루틴 실행")
        }
    }
    launch(CoroutineName("Coroutine2") + newRootJob) {
        launch(CoroutineName("Coroutine5")) {
            delay(100L)
            println("[${Thread.currentThread().name}] 코루틴 실행")
        }
    }
}   

Job 생성 함수

  • Job 생성 함수는 부모 Job을 인자로 받을 수 있다.
    • parent 인자가 입력되지 않으면 parent가 null이 되어 루트 Job이 생성된다.
    • parent 인자가 입력되면 해당 job을 부모로 하는 Job이 생성된다.
public fun Job(parent: Job? = null): CompletableJob = JobImpl(parent)
  • Job 생성 함수를 이용해 생성된 Job객체는 자동으로 실행 완료되지 않는다.
  • Job 객체를 종료하기 위해서는 complete() 함수를 호출해서 명시적으로 완료 호출을 해줘야 한다.

runBlocking 함수

  • runBlocking 함수가 호출되면, 호출부의 스레드를 차단하고 배타적으로 사용하는 코루틴이 만들어진다.
    • 호출부의 스레드를 사용할 수 있는 코루틴은 자신과 자식 코루틴들뿐이다.
  • 호출부의 스레드는 runBlocking 함수가 생성한 코루틴이 실행 완료될 때까지 다른 작업에 사용될 수 없다.

'안드로이드' 카테고리의 다른 글

일시 중단 함수와 코루틴  (0) 2025.01.13
코루틴 예외 처리  (0) 2025.01.12
CoroutineContext란  (0) 2025.01.10
코루틴으로부터 결과 수신 받기  (0) 2025.01.08
코루틴 제어  (0) 2025.01.07