코루틴
보통 코루틴을 경량화 스레드라고 자주 표현한다
그럼 기존의 스레드는 무엇을 의미할까?
기존의 스레드는 보통 OS 혹은 JVM 에서 생성되는 스레드를 의미하고, 이 스레드들은 매우 빠르고, 반응이 좋지만 비용이 크다.
각 스레드는 생성, 해제(GC 수집시), 컨텍스트 스위치(스레드 실행 혹은 종료시에 스레드의 상태를 저장하고 복구하는 프로세스가 있다) 시에 CPU 타임과 메모리를 소모하는데, 이 비용이 상대적으로 높기 때문에 JVM 에서 돌아가는 프로그램들은 아주 많은 수의 스레드를 가지기 어렵다.
반면에 코루틴은 값을 반환할 수 있고 일시 중지(pause) 및 복귀(resume)할 수 있는 경량화된 스레드(정확하게 스레드는 아니고 함수이며, 비동기를 풀어나가는 코딩스타일중 하나고 그것에 초점을 맞춰 나온 라이브러리)이다.
사용시에 권장되는 코드 스타일은
Dispatcher.Main 에서 대부분의 함수를 작성하고, 다른 스레드를 사용해야하는 상황(네트워크 및 기타 IO)이라면,
따로 디스패처를 지정해서 처리하는 방식이 스위칭 컨텍스트를 줄일 수 있어 퍼포먼스 향상에 도움이 된다고 한다.
키워드
RunBlocking : 코루틴을 생성한 후 코루틴이 끝나고, 그 결과 값을 반환할 때 까지 현재 스레드를 블록한다 (잘 사용해야함)
Launch : 현재 스레드의 블록 없이 새 코루틴을 생성하고 Job 을 반환한다
Delay : 현재 스레드를 블록하지 않고 현재 코루틴을 지연시키는 함수이다
Suspend : 현재 스레드의 블록 없이 코루틴의 실행을 일시 중지 할 수 있는 함수이다. 따라서 이 함수는 코루틴 내부에서 호출되어야 한다. 일반 코드에서는 실행할 수 없다. 꼭 Suspend 키워드가 붙어 있어야 한다. 따라서 runBlocking, launch 내부에서 실행될 수 있다.
코루틴 컨셉
Coroutine : 값을 반환할 수 있고 일시 중지 및 복귀할 수 있는 매우 가벼운 스레드
Suspending function : suspend 키워드가 표시된 함수로, 스레드 차단 없이 코루틴을 일시 중지 할 수 있다. Suspend 함수는 코루틴 내부에서 호출 된다
Suspending lambda : suspend 가 표시된 람다 함수로, 스레드 차단 없이 코루틴을 일시 중지할 수 있다.
Coroutine builder : Suspending lambda 를 받고 코루틴을 생성하고, runBlocking 같은 결과를 반환할 수도 있는 함수이다
Suspension point : 일시 중지 함수가 호출된 지점
Continuation : 일시 중지된 코루틴의 상태로 이후의 나머지 실행을 표시한다.
인텔리제이에서 기본설정으로 스레드를 2000개 이상 만들면 OOM(1.5GB 차지)이 발생하지만, 코루틴은 10000개(50 MB 도 채 되지 않는다)도 거뜬하게 만들 수 있다.
심지어 OS 단에서 스레드를 많이 생성하는 것은 나쁜 습관으로 간주되고 있기 때문에, Executor 같은 클래스를 이용하여, 미리 천개정도할당 하여 일을 처리하는 수 밖에 없다.
스레드 천개에 일을 할당하고, 기다리다가 끝나는 스레드에게 다시 일을 부여하는 식이지만 코루틴은 이것을 한번에 해낸다.
결론은 코루틴은 매우, 아주 가볍다
보통 비동기 작업을 하기 위해 많이 사용하는 코드 스타일중 하나는 콜백 형식이지만,
함수가 깊어 질수록 떨어지는 가독성과 처리의 어려움(코드의 복잡성)과 callback 이라는 파라미터를 항상 넘겨야 한다는 점으로 인해,
자바에는 Future, CompletableFuture 같은 것들이 나왔고, 현재는 프로미스(promise) 방식도 많이 사용하고 있다(특히 자바스크립트 then 으로 연결하면서 풀어가는 방식)
그러나 코루틴은 Sequential 하게 로직을 작성할 수 있어 가독성이 아주 좋고 직관적이다.
코루틴은 항상 컨텍스트에서 실행된다.
여기서 컨텍스트는 무엇인가? 간단하게 이야기하면 Dispatcher(코루틴을 실행하는 스레드를 제어하는 놈) + Error(coroutine exception handler)를 제어할 수 있는 놈이다.
이런 특징을 가진 코루틴 컨텍스트를 가지고 있는 놈이 코루틴 스코프이다.
코루틴 스코프(coroutine scope)
- 모든 코루틴은 코루틴 스코프 안에서 처리된다
- 스코프는 작업(job)을 통해 코루틴의 라이프 사이클을 제어한다.
- 스코프에 있는 작업을 취소하면 해당 스코프안에서 시작된 코루틴은 모두 취소된다.
현재는 ViewModelScope 도 추가적으로 제공한다
기본적으로 Dispatcher.Main(MainThread) 에서 처리되고, onCleared() 시점에 자동으로 취소된다.
viewModelScope.launch 로 처리시 기본으로 Dispatchers.Main 에서 처리되며, delay() 는 코루틴에서 제공하는 suspend 함수이기 때문에 안드로이드 MainThread 를 중지시키지 않는다. 대신 1초뒤에 코루틴이 다시 재개하도록 한다(재개하면 _taps.postValue 를 만날 것이다.)
fun updateTaps() { // launch a coroutine in viewModelScope viewModelScope.launch { tapCount++ // suspend this coroutine for one second delay(1_000) // resume in the main dispatcher // _snackbar.value can be called directly from main thread _taps.postValue("$tapCount taps") } }
자바 5 이상에서 코루틴에서 사용할 수 있는 아토믹 프리미티브 클래스들을 제공한다
여러 코루틴에서 같은 리소스를 공유하지만 동시에 접근하지 못하는 critical section 도 만들수 있는 뮤텍스를 제공한다.
코루틴이 중단을 하고 다시 재게하는 방식은 JVM의 컴파일러가 CPS 변환을 이용하여 처리한다
나올 수 있는 모든 상태를 switch 문으로 나누고 처리한다.
관련 예는 https://jess-m.tistory.com/25 에 잘 나와있다.
TODO
채널, 채널 파이프라인, 액터
'Kotlin & Java' 카테고리의 다른 글
자바 문자열 고찰 및 생각의 힘 (0) | 2020.02.28 |
---|---|
자바 기본 타입 (Primitive type) (0) | 2020.02.27 |
무공변성 공변성 반공변성 (0) | 2020.01.24 |
[Java ] 두개의 리스트에서 중복되는 객체 비교 (0) | 2018.11.08 |
What is CALL BACK? 콜백 (0) | 2017.05.12 |