Programming Language/Kotlin

[Kotlin] Dive into Concept of Kotlin Coroutine Context. Dispatchers, Job and Deferred.

개발왕 금골드 2023. 7. 1. 18:18
반응형
  • 소개
    • Dispatchers
      • Android Dispatchers
      • Dispatchers.Default
      • Dispatchers.IO
      • Dispatchers.IO & Dispatchers.Default
    • Job
      • Deferred
      • Job States

소개

CoroutineContext는 코루틴 실행을 어떻게 할 것인지에 대한 요소들을 결정한다. Coroutine은 항상 Kotlin 표준 라이브러리에 정의된 CoroutineContext 타입의 값으로 표시되는 일부 Context에서 실행된다. CoroutineContext는 다양한 요소의 집합이다. 주요 요소는 Job, Dispathcer, ExceptionHandler 등이 있다. 이러한 요소들은 각각의 키 값을 갖고 있으며, CoroutineContext로 등록된다.

/*
 * Copyright 2010-2018 JetBrains s.r.o. and Kotlin Programming Language contributors.
 * Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
 */

package kotlin.coroutines

/**
 * Persistent context for the coroutine. It is an indexed set of [Element] instances.
 * An indexed set is a mix between a set and a map.
 * Every element in this set has a unique [Key].
 */
@SinceKotlin("1.3")
public interface CoroutineContext {
    /**
     * Returns the element with the given [key] from this context or `null`.
     */
    public operator fun <E : Element> get(key: Key<E>): E?

    /**
     * Accumulates entries of this context starting with [initial] value and applying [operation]
     * from left to right to current accumulator value and each element of this context.
     */
    public fun <R> fold(initial: R, operation: (R, Element) -> R): R

    /**
     * Returns a context containing elements from this context and elements from  other [context].
     * The elements from this context with the same key as in the other one are dropped.
     */
    public operator fun plus(context: CoroutineContext): CoroutineContext =
        if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
            context.fold(this) { acc, element ->
                val removed = acc.minusKey(element.key)
                if (removed === EmptyCoroutineContext) element else {
                    // make sure interceptor is always last in the context (and thus is fast to get when present)
                    val interceptor = removed[ContinuationInterceptor]
                    if (interceptor == null) CombinedContext(removed, element) else {
                        val left = removed.minusKey(ContinuationInterceptor)
                        if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
                            CombinedContext(CombinedContext(left, element), interceptor)
                    }
                }
            }

    /**
     * Returns a context containing elements from this context, but without an element with
     * the specified [key].
     */
    public fun minusKey(key: Key<*>): CoroutineContext

    /**
     * Key for the elements of [CoroutineContext]. [E] is a type of element with this key.
     */
    public interface Key<E : Element>

    /**
     * An element of the [CoroutineContext]. An element of the coroutine context is a singleton context by itself.
     */
    public interface Element : CoroutineContext {
        /**
         * A key of this coroutine context element.
         */
        public val key: Key<*>

        public override operator fun <E : Element> get(key: Key<E>): E? =
            @Suppress("UNCHECKED_CAST")
            if (this.key == key) this as E else null

        public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
            operation(initial, this)

        public override fun minusKey(key: Key<*>): CoroutineContext =
            if (this.key == key) EmptyCoroutineContext else this
    }
}

CoroutineContext는 인터페이스로써 이를 구현한 세 개의 구현체가 있다.

  • EmptyCoroutineContext : Context가 명시되지 않을 경우.
  • CombinedContext : 두 개 이상의 Context가 명시되면 Context 사이 연결을 위한 Container 역할을 하는 Context.
  • Element : Context의 각 요소들 또한 CoroutineContext를 구현한다.

launch라는 CoroutineBuilder 안에 어떠한 CoroutineContext를 보내느냐에 따라 그 상태 값을 보여주는 이미지이다. CoroutineContext는 plus 연산자를 갖고 있기 때문에 CoroutineContext를 상속한 요소들이 + 연산자로 병합될 수 있다. 노란 박스가 하나의 CoroutineContext이다. 최종적으로 하나의 CoroutineContext가 완성된다. (Continuation Intercaptor는 병합 작업이 발생할 때, 항상 마지막에 위치하도록 고정된다. 이는 빠른 접근을 위해서이다.)

Dispatcher

Coroutine Dispatcher는 해당 코루틴이 실행에 사용할 스레드를 결정한다. 코루틴 실행을 특정 스레드로 제한하거나, 스레드 풀로 디스패치하거나, 제한 없이 실행되도록 할 수 있다. 모든 CoroutineBuilder는 Dispatcher를 명시적으로 지정하여 사용할 수 있는 CoroutineContext 매개변수를 허용한다.

 

Android Dispatchers

안드로이드에는 이미 Dispatcher가 생성되어 있어 별도로 생성하거나 정의하지 않아도 된다.

  • Dispatchers.Main : 안드로이드 메인 스레드로, UI 작업을 실행하기 위해서 사용. 일반적으로 싱글 스레드이다.
  • Dispatchers.IO : 디스크 또는 네트워크 작업을 공유 스레드 풀로 오프로드하도록 설계된 Coroutine Dispatcher.
  • Dispatchers.Default : Dispatcher를 지정하지 않은 모든 표준 Builder에서 사용한다. CPU를 많이 사용하는 작업을 기본 스레드 외부에서 실행하도록 최적화되어 있다. 예를 들어 목록을 정렬하고 JSON을 파싱 한다.
CoroutineScope(Dispatchers.Main).launch {
  UpdateView()
}

Dispatchers.Default

JVM에서 제공되는 공용 백그라운드 스레드 풀을 사용한다. 기본적으로 사용하는 최대 스레드 수는 CPU 코어 수와 동일하지만 최소 2개이다. Dispatchers.Default의 경우 대기 시간이 없고 CPU의 작업을 필요로 하는 무거운 작업에 적합하다. 코어 수 만큼의 스레드만 생성하고 작업하기 때문에 CPU를 많이 점유하는 작업에서 최대의 효율을 낼 수 있기 때문이다.

Dispatchers.IO

Dispatchers.IO 스레드 풀에 추가 스레드가 생성되어 요청 시 제거된다. 이 Dispatcher의 작업에 사용되는 스레드 수는 "kotlinx.coroutines.io.discriptism"(IO_PARALLISM_PROPERTY_NAME) 시스템 속성 값에 의해 제한된다. 기본적으로 64개 스레드 또는 코어 수(더 큰 것 중 하나)로 제한된다.

Dispatchers.IO와 Dispatchers.Default

IO와 Default는 스레드 풀의 스레드를 공유한다. 그렇기 때문에 만약 Dispatchers.Default로 선언된 Builder 안에서 Dispatcher.IO를 선언한다면, 일반적으로 동일한 스레드에서 실행이 계속된다. 스레드 공유의 결과, Dispatcher.IO를 통한 작업 중 64개 이상의 스레드를 생성할 수 있다(기본 병렬 처리). 최대 실행 스레드는 64개이다.
(원문(Dispatchers.kt) : This dispatcher and its views share threads with the Default dispatcher, so using withContext(Dispatchers.IO) { ... } when already running on the Default dispatcher typically does not lead to an actual switching to another thread. In such scenarios, the underlying implementation attempts to keep the execution on the same thread on a best-effort basis.
As a result of thread sharing, more than 64 (default parallelism) threads can be created (but not used) during operations over IO dispatcher.)
 

Job

Job은 코루틴을 컨트롤하기 위해 사용한다. Job을 통해 코루틴을 제어할 수 있다. Job은 상위-하위 계층으로 정렬될 수 있는데, 상위 계층이 에러가 발생하거나 취소되면 하위 계층이 재귀적으로 즉시 취소된다. 이러한 동작은 SupervisorJob을 사용하여 사용자 지정할 수 있다.
Job 객체는 launch CoroutineBuilder 혹은 CompletableJob을 생성할 수 있는 Job() factory 함수로 생성할 수 있다.

val job = CoroutineScope(Dispatchers.Main).launch {
  // Do Something
  launch {
    // Do Something
  }
}

 

Deferred (Job을 상속한다.)

Deferred는 차단되지 않는 취소 가능한 Future이다. 결괏값을 갖는 Job 객체이다. (Deferred value is a non-blocking cancellable future — it is a Job that has a result.) 말이 어렵지만, 간단하게 Job을 상속하고 있으며 계산한 결과를 저장하는 것이라고 생각한다.
Deferred 객체는 async CoroutineBuilder 혹은 CompletableDeferred 클래스의 constructor를 통해서 생성할 수 있다.

val deferred : Deferred<String> = async {
  // Do Something
  "result"
}
        
val message = deferred.await() // deferred 객체가 완료될 때 까지 기다린다.
println(message) // result가 출력된다.

Job States

 
CoroutineBuilder 속성 중 Lazy 속성이 있다. Lazy 속성은 해당 Job에 접근한 순간에 메모리에 올리겠다는 뜻이다. 일반 lazy와 동일한 기능을 한다.

val job = CoroutineScope(Dispatchers.IO).launch(start = CoroutineStart.LAZY) {
  // Do Something
}

 


참고자료 :
https://myungpyo.medium.com/reading-coroutine-official-guide-thoroughly-part-1-7ebb70a51910

 

코루틴 공식 가이드 읽고 분석하기- Part 1 — Dive1

CoroutineContext와 CoroutineScope 란 무엇인가?

myungpyo.medium.com

https://kotlinlang.org/docs/coroutine-context-and-dispatchers.html#dispatchers-and-threads

 

Coroutine context and dispatchers | Kotlin

 

kotlinlang.org

https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/

 

Job

Job A background job. Conceptually, a job is a cancellable thing with a life-cycle that culminates in its completion. Jobs can be arranged into parent-child hierarchies where cancellation of a parent leads to immediate cancellation of all its children recu

kotlinlang.org

https://sandn.tistory.com/110

 

Coroutine의 IO Dispatcher와 Default Dispatcher 의 사용 시 차이

Dispatcher Dispatcher는 코루틴을 특정 스레드에서 실행할 수 있도록 도와주는 기능이다. 코루틴에서는 디스패처를 이용하여 다양하게 스코프를 지정할 수 있다. Rx류의 라이브러리에서 쓰이는 스케

sandn.tistory.com

https://developer.android.com/kotlin/coroutines/coroutines-adv?hl=ko 

 

Kotlin 코루틴으로 앱 성능 향상  |  Android Developers

Kotlin 코루틴으로 앱 성능 향상 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Kotlin 코루틴을 사용하면 네트워크 호출이나 디스크 작업과 같은 장기 실행 작

developer.android.com

 

반응형