코루틴이 무엇인가요!
- 코루틴은 동시성 프로그래밍을 가능케 하도록 만든 개념
이게 어떤게 좋은가요?
- 가독성 측면: Coroutine은 콜백 기반 코드를 sequential code로 바꾸어주기 때문에 비동기 코드를 단순화
- 쓰레드 nonblocking : 네트워크, DB작업등에 소요될때 쓰레드를 물고 있지 않는다. suspend 중단함수를 만나게 되면 현재 쓰레드를 물지 않고 해제한다. 다른 쓰레드에서 중단 함수의 수행이 완료되고 다시 쓰레드를 해제한다. 이후 또 다른 쓰레드에서 중단되었던 함수 다음에 재개한다.
📌 Thread vs Coroutine
- 애플리케이션에는 하나 이상의 프로세스가 있고, 각각의 프로세스는 적어도 하나의 스레드가 존재한다.
- Coroutine은 스레드 안에서 실행된다. 같은 스레드에 10개, 100개의 Coroutine이 있을 수 있다.
- 동시성 프로그래밍에서 한 시점에는 하나의 Coroutine만 실행
- Coroutine은 특정 스레드에 종속되지 않는다. Coroutine은 resume될 때마다 다른 스레드에서 실행
- 스레드를 blocking 하지 않으면서 작업의 실행을 잠시 중단. 스레드 B가 스레드 A의 작업이 끝날때까지 대기해야하는 작업을 잠시 중단하고, 그동안 다른 작업을 할수 있다
- 스레드를 blocking하지 않아 더 빠른 연산으로 이어지게하고, 메모리 사용량을 줄여 많은 동시성 작업을 수행
CPS - Continuation
- Coroutine에는 CPS(Continuation Passing Style) 패러다임이 적용.
- CPS란 호출되는 함수에 Continuation을 전달하고, 각 함수의 작업이 완료되는 대로 전달받은 Continuation을 호출하는 패러다임을 의미합니다. 이러한 의미에서 Continuation을 일종의 콜백
- Continuation은 다음에 무슨 일을 해야 할지 담고 있는 확장된 콜백
- CPS에선 Continuation을 전달하면서, 현재 suspend된 부분에서의 resume을 가능하게 해준다.
- Continuation은 resume되었을 때의 동작 관리를 위한 객체로, 연속적인 상태간의 communicator
- Continuation은 호출 함수간의 suspend-resume을 위한 communicator이고, CPS는 함수 호출 시에 이 Continuation을 전달하는 패러다임
State machine
- Kotlin은 모든 중단 가능 지점에 라벨링 하고 재개시 label의 값을 when 으로 분기한다.
- 중단 가능 지점에서 suspend되고 해당 작업이 끝나 resume되면 다시 다음 블록을 실행
- 상태를 관리하는 하나의 방법으로 상태머신
CoroutineScope (interface)
- 모든 코루틴은 Scope 내에서 실행이 되어야 한다. 즉 코루틴은 Coroutine Scope 을 통해서 코루틴이 사용되는 범위를 지정해줘야 한다.
- 하나의 CoroutineContext 멤버속성만 갖는 인터페이스다.
- 코루틴 빌더들은 CoroutineScope의 확장함수로, 다양한 요구사항에 맞게 개별적인 Coroutine(코루틴)을 만든다.
- 전역 스코프 생성은 GlobalScope 와 CoroutineScope()이 존재한다.
GlobalScope
- GlobalScope는 CoroutineScope의 한 종류로써 가장 큰 특징은 Application이 시작하고 종료될 때까지 계속 유지가 된다.
- 최상위 코루틴이며 GlobalScope.launch{} 실행한 코루틴은 어플리케이션이 종료될 때까지 살아있다.
- 전역 스코프라서 사용하는 즉시 새로운 스레드가 생성되고 그 안에서 비동기 작업이 가능해진다.
- EmptyCoroutineContext를 기본 컨텐스트로 갖고 있다.
- Singletone이기 때문에 따로 생성하지 않아도 되며 어디에서든 바로 접근이 가능하여 간단하게 사용하기 쉽다는 장점이 있다.
- GlobalScope를 사용하면 메모리 누수의 원인이 될 수 있기 때문에 신중히 사용해야 한다. 즉 다시 말해 앱이 실행된 이 후 계속 수행이 되어야 한다면 GlobalScope 를 사용해야 하는 것
CoroutineScope (function)
- CoroutineScope()는 코루틴 블럭(영역)을 생성하는 역할
- CoroutineScope()는 서버와 통신한다거나 하는 등의 필요할 때만 시작, 완료되면 종료하는 용도로 사용된다.
- CoroutineScope()는 객체를 생성하기 때문에 호출한 객체가 메모리에서 해제가 되면 같이 파괴가 됩니다. 하지만, 원하는 대로 작동하려면 적절하게 cancel을 호출해줘야 해당 코루틴 작업이 취소된다.
GlobalScope vs CoroutineScope()
- GlobalScope는 EmptyCoroutineContext를 CoroutineContext로 사용
- CoroutineScope()는 CoroutineContext가 강제되고, 넘겨 받은 CoroutineContext에 Job이 없으면 추가 해준다.
📌 CoroutineContext의 유무 때문에 생기는 차이점
- Dispatcher : EmptyCoroutineContext로 코루틴 람다를 실행하면 기본적으로 Dispatchers.Default를 지정
GlobalScope.launch {} 를 하면 Dispatcher.Default로 지정되어 코루틴 람다를 실행, CoroutineScope는 생성할 때 넘겨준 Dispatcher로 실행
- Job : GlobalScope는 Job이 없는 CoroutineContext를 가지고 있기 때문에, GlobalScope.cancel() 이런 식으로 코루틴 작업을 취소할 수 없다. GlobalScope.launch 에서 반환되는 job을 가지고만 취소 해야만 한다.
CoroutineScope()는 생성할 때 Job이 있는지 체크하여 없으면 Job을 넣어주기 때문에, 바로 cancel이 가능하다.
CoroutineContext
- CoroutineContext는 Job, CoroutineName, CoroutineDispatcher, CoroutineInterceptor, CoroutineExceptionHandler 등과 같은 Element 인스턴스를 순서가 있는 Set(indexed set)으로 관리
- Dispatcher 는 코루틴에 대한 task수행 분배를 어떻게 할 것인지 결정하는 역할, 정확히는 Interceptor 에 의해서 Coroutine 은 동작에 대한 중지 혹은 동작에 대한 재개를 실행
- CoroutineContext는 Coroutine이 실행되는 환경
public interface CoroutineContext {
/**
* Returns the element with the given [key] from this context or `null`.
* Keys are compared _by reference_, that is to get an element from the context the reference to its actual key
* object must be presented to this function.
*/
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 = ...impl...
/**
* Returns a context containing elements from this context, but without an element with
* the specified [key]. Keys are compared _by reference_, that is to remove an element from the context
* the reference to its actual key object must be presented to this function.
*/
public fun minusKey(key: Key<*>): CoroutineContext
}
CoroutineContext 구현체
- CoroutineContext는 인터페이스로 이를 구현한 구현체는 기본적으로 3가지의 종류가 존재
1) EmptyCoroutineContext - Default Context / Singleton
- EmptyCoroutineContext 는 구현해야할 모든 CoroutineContext 멤버 함수들에 대해서 기본 구현만 정의한 컨텍스트
- 이 기본 컨텍스트는 어떤 생명주기에 바인딩 된 Job 이 정의되어 있지 않기 때문에 애플리케이션 프로세스와 동일한 생명주기를 갖게 된다.
2) CombinedContext - 두개 이상의 컨텍스트가 명시되면 Context간 연결을 위한 컨테이너 역할의 Context.
- 각각의 요소를 + 연산자를 이용해 연결하고 있는데 이는 앞서 설명한 것처럼 CoroutineContext가 plus 연산자를 구현하고 있기 때문이다. ex: launch(Dispatchers.Main + job)
- Element + Element + … 는 결국 하나로 병합 된 CoroutineContext (e.g. CombinedContext)를 만든다.
3) Element - Context의 각 요소(Element)들도 CoroutineConext를 구현
Key / Element
- Key는 Element 타입을 제네릭 타입
- Element는 CoroutineContext를 상속하며 Key를 멤버 속성
- 코루틴 컨텍스트를 구성하는 Element는 CoroutineContext는 Job, CoroutineName, CoroutineDispatcher, CoroutineInterceptor, CoroutineExceptionHandler 등
- Element들은 각각의 Key를 기반으로 CoroutineContext에 등록
- CoroutineContext는 Element들이 등록될 수 있고, 각 요소들이 등록될 때 요소의 고유한 Key를 기반으로 등록된다.
/**
* Key for the elements of [CoroutineContext]. [E] is a type of element with this key.
* Keys in the context are compared _by reference_.
*/
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<*>
...overrides...
}
📌 CoroutineContext plus
Dispatcher: 코루틴이 실행될 쓰레드 풀을 잡고 있는 관리자
CoroutineExceptionHandler: 코루틴에서 Exception이 생겼을 때의 처리
- Key("KeyB")를 요청하면 CoroutineExceptionHandler를 반환받을 수 있다.
Dispatcher
- CoroutinScope의 경우 GlobalScope와는 다르게 Dispatcher 라는 것을 지정할 수 있는데, 이는 코루틴이 실행될 스레드를 지정하는 역할을 한다.
- 종류 :
Dispatchers.Default: CPU를 많이 쓰는 작업에 최적화. (데이터 정렬, 복잡한 연산 등..)
Dispatchers.IO: 이름처럼 IO 작업을 할 때에 최적화. (다운로드, 파일 입출력, 네트워킹, DB 작업 등..)
Dispatchers.Main: 메인 스레드. (화면 UI 작업)
Dispatchers.Unconfined: 호출한 context를 기본으로 사용하는데 중단 후 다시 실행될 때 context가 바뀌면 바뀐 context를 따라가는 특이한 Dispatcher.
CoroutineBuilder
- 코루틴 빌더들은 CoroutineScope의 확장함수로, 다양한 요구사항에 맞게 개별적인 Coroutine(코루틴)을 만든다.
- Coroutine Builder에는 여러 종류 존재 ( launch / async / withContext / runBlocking / actor / produce )
빌더는 다음 이시간에 :0 )
'개발' 카테고리의 다른 글
[kotlin] Kotlin Coroutine - 2 (0) | 2023.01.18 |
---|---|
[kotlin] Coroutines under the hood - 번역 (0) | 2022.12.28 |
[kotlin] 코루틴 개념 정리 (0) | 2021.11.09 |