반응형
Coroutine
을 사용하는 이유
비동기 프로그래밍을 사용하는 이유
- 안드로이드에서
Main Thread
는UI Thread
이다. - 그렇기 때문에,
Main Thread
에서 UI를 그리는 것 이외에 복잡하고, 시간이 오래 걸리는 작업을 수행하는 것은 적합하지 않다.- 만약, 이러한 작업을 수행하게 될 경우, 해당 작업이 끝날 때까지 시스템은 사용자에게 보여질 UI를 그리지 못하게되고, 사용자는 앱을 시작했음에도 긴 시간동안 빈화면을 보게 되는 일이 발생한다.
(UI Freezing) - 그래서 안드로이드에서는 해당 작업들을
Main Thread
에서 하지 못하도록 한다. - 이러한 복잡한 작업의 에로는 네트워킹이 대표적이다. 만약 네트워크 작업을
Main Thread
에서 진행하려고 할 경우,NetworkOnMainTrheadException
이 발생하게 된다. - 이외에 내부 저장소 접근도 포함된다.
- 만약, 이러한 작업을 수행하게 될 경우, 해당 작업이 끝날 때까지 시스템은 사용자에게 보여질 UI를 그리지 못하게되고, 사용자는 앱을 시작했음에도 긴 시간동안 빈화면을 보게 되는 일이 발생한다.
이러한 문제를 해결하기 위해 사용되는 것이 바로
비동기 프로그래밍
이다.Main Thread
는 그대로 동작하면서,Main Thread
이외의Background thread
에서 작업을 수행하는 것이다.
비동기 작업을 수행하는 방식
이때, 비동기 작업을 수행하는 방식에 여러 가지가 존재하는데, 그중 가장 많이 사용되는 방식이 3가지이다.
- Callback
- 네트워크 작업을
Background Thread
에서 동작시키고, 그 결과를callback
을 통해 전달한다 - 과거부터 비동기적 프로그래밍 방식에서 굉장히 많이 사용하는 방식이다.
- 이른바 "콜백 지옥" (Callback Hell) 이 펼쳐질 수 있다. 이 경우 코드 가독성이 굉장히 떨어지고, 유지보수가 어려워진다
- 네트워크 작업을
- RxJava, RxKotlin
- indentation이 사라지기 때문에
Callback
방식보다 코드 가독성은 좋다 - 비동기 처리를 위해 굉장히 많이 사용하는 API
- 높은 효율성과 익숙해질 경우 여러 장점이 존재한다.
- 러닝커브가 굉장히 높다.
Call
이 누적될수록,Exception
발생 시, 영향 파악이 어려워지며, 모든Transformation
에 대한 파악도 어렵다는 단점이 잇다.- 중간중간에 끼어있는
lambda block
들은동기적 코드
에 비해 여전히 코드 가독성이 좋지않다.
- indentation이 사라지기 때문에
- Coroutine
Multi Threading
기법에서 CPU를 많이 소모하는 Context Switching이 적어 속도가 훨씬 빠르다- CPU 자원을 좀 더 효율적으로 사용하기에
Light Weight Thread
라는 표현을 사용하기도 한다.- 다만, 추후에도 언급하지만
Coroutine
은Thread
가 아니라는 점 인지해야 한다. Thread
안에서 실행될 수 있는Procedure
에 가깝다.- 각 작업에
thread
를 할당하는게 아니라object
를 할당하여 Context Switching을 최대한 줄이면서(Object Switching
) DeadLock 문제도 자연스럽게 해결한다.
- 다만, 추후에도 언급하지만
Thread
가 아무것도 하지 않는 상태를 거의 방지하여,Thread
를 최대한 활용할 수 있다- 동기코드 작성시와 동일한 가독성과 간결함을 유지할 수 있다.
- 러닝커브가 상대적으로 낮다.
- Kotlin에 직접적으로 통합되어 있다.
- Retrofit, Androidx 등 여러 라이브러리과 쉽게 통합이 되고, 유지관리가 좀 더 쉬워진다.
예시로 이해하기 (회원가입 완료 후 로그인 처리까지 하는 상황)
- 처리 순서
- 사용자가 가입할 이메일과 비밀번호 입력
- 이메일, 비밀번호 기반으로 회원가입 요청
- 회원가입 성공후, 해당 이메일과 비밀번호를 사용해 로그인 요청
- 성공메시지 노출
- 논리적으로 생각한 프로그램 코드
// 1. 이메일, 비밀번호를 입력 받습니다.
val email: String = "greedy0110@gmail.com"
val password: String = "0110"
// 2. 이메일, 비밀번호를 기반으로 회원가입을 요청합니다.
server.requestSignUp(email, password)
// 3. 회원가입이 성공적으로 완수되면, 해당 이메일과 비밀번호를 사용해 로그인을 요청합니다.
server.requestLogIn(email, password)
// 4. 성공 메시지를 노출시킵니다.
successMessage.isVisible = true
- 이 코드를 그대로 사용할수 있을까?
- 우리는
UI
를 그리고, 사용자 입력을 받아오기 위한MainThread
를 별도로 두고 있는 상황에서, 2번과 3번의 서버 응답을MainThread
에서 기다린다면 네트워크가 좋지 않은 환경에서는 사용자는 같은 화면만 보고있는 상황 발생한다 - 안드로이드에서는 이경우
ANR 에러
를 발생시켜, 앱이 종료된다.
(이러한 상황을 방지하기 위해서, 애초에MainThread
에서 네트워크 작업을 할 수 없게 되어있다) 별도의 Thread
로 만들어서 실행한다면?
thread {
// 2. 이메일, 비밀번호를 기반으로 회원가입을 요청합니다.
server.requestSignUp(email, password)
// 3. 회원가입이 성공적으로 완수되면, 해당 이메일과 비밀번호를 사용해 로그인을 요청합니다.
server.requestLogIn(email, password)
// 4. 성공 메시지를 노출시킵니다.
successMessage.isVisible = true
}
- 4번째 동작인 UI를 변경시키는 동작이
MainThread
에서 실행되어야 하는 동작이기에 에러를 내게된다. - 그렇다면 UI를 변경시켜야 하는 동작만 따로 뺀다면?
thread {
// 2. 이메일, 비밀번호를 기반으로 회원가입을 요청합니다.
server.requestSignUp(email, password)
// 3. 회원가입이 성공적으로 완수되면, 해당 이메일과 비밀번호를 사용해 로그인을 요청합니다.
server.requestLogIn(email, password)
}
// 4. 성공 메시지를 노출시킵니다.
successMessage.isVisible = true
- 실행은 되지만, 4번은 회원가입 성공시에만 노출이 되어야 한다.
- 지금 상황에서는 어떻게 되든지 4번은 무조건 실행된다.
thread
실행의 동작(2번과 3번)과MainThread
(4번) 동작이별도의 Thread
에서 병렬적으로 실행되기 때문이다.- 우리가 원하는 것은 2, 3, 4가 순차적으로 실행되는 것을 보장하는 것이다.
- → 이는 결국
동시성 보장
을 원하는 것과 같다. - 이 문제를 해결하기 위해 앞서 설명한 것들이 사용된다.
1. CallBack
// 1. 이메일, 비밀번호를 입력 받습니다.
val email: String = "greedy0110@gmail.com"
val password: String = "0110"
// 2. 이메일, 비밀번호를 기반으로 회원가입을 요청합니다.
server.requestSignUp(email, password) {
// 3. 회원가입이 성공적으로 완수되면, 해당 이메일과 비밀번호를 사용해 로그인을 요청합니다.
server.requestLogIn(email, password) {
// 4. 성공 메시지를 노출시킵니다.
successMessage.isVisible = true
}
}
- 이렇게 한다면, 깔끔하게 처리할 수 있을것만 같다.
- 그러나, 현재는
동시성 처리
가 2개이므로 깔끔해보이지만, 동시성을 처리해야 하는 요청이 몇개만 추가되어도 아래와 같은 코드가 탄생해버린다.
- 이것을 우리는 그 악명높은
CallBack Hell
이 발생했다고 한다.
2. Coroutine
uiScope.launch {
// 1. 이메일, 비밀번호를 입력 받습니다.
val email: String = "greedy0110@gmail.com"
val password: String = "0110"
// 2. 이메일, 비밀번호를 기반으로 회원가입을 요청합니다.
server.requestSignUp(email, password)
// 3. 회원가입이 성공적으로 완수되면, 해당 이메일과 비밀번호를 사용해 로그인을 요청합니다.
server.requestLogIn(email, password)
// 4. 성공 메시지를 노출시킵니다.
successMessage.isVisible = true
}
- 처음 논리적으로 작성했던 코드와 똑같이 작성이 가능하다.
- 이 코드는
I/O Thread
를 통한 서버요청,MainThread
를 사용한 화면 갱신 등모든 비동기 코드
의동시성 처리
가 된 코드라고 볼 수 있다.Coroutine
은 이렇게 사용될수 있다.
Coroutine
의 사용 배경
- 안드로이드에서는 비동기 프로그래밍을 위한 방법으로
Coroutine
을 제안한다.- 따라서
Coroutine
은 비동기 처리를 하는데 사용되는동시 실행 디자인 패턴
이라고 볼 수 있다.
- 따라서
Coroutine
은 Kotlin뿐만 아니라, Python, C#, JS 등 여러 언어에서 지원하는 개념이다.Coroutine
을 사용하면 간단한 방법으로 동시성 관리가 가능하다.- 여기서 “간단하다" 라는 것은 상대적인 의미이다.
- 안드로이드에서는 기존에 비동기 처리를 할 수 있는 여러 API가 존재하는데, 그 중 대표적인 것이
Handler & Loop
와AsyncTask
이였다. - 그러나, 안드로이드에서
Thread
프로그래밍의 위험성 때문에AsyncTask
를 Deprecated시켰다. - 결국에, 안드로이드 개발자들에게 비동기 처리를 위한
ReactiveX
라이브러리(RxJava
,RxKotlin
)가 거의 필수적이었는데, 이는 마법같은 Operation들이 상당히 많기 때문에, 러닝커브가 굉장히 높기로 유명하다. - 이에 비해
Coroutine
은 간단하고, 가독성 높게 비동기 처리를 구현할 수 있으며,
이러한 이유로 간단하게 동시성 관리가 가능하다는 말이 붙은 것이다.
- 그리고, 구글에서 공식적으로 권장하고 있으며 실제 예시 코드들도
Rx
에서Coroutine
으로 변경되고 있다.
Coroutine
과 관련된 배경 지식
Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing execution to be suspended and resumed (Wikipedia)
= 코루틴이란, 실행의 지연과 재개를 허용함으로서, 비 선점적 멀티태스킹을 위한 서브루틴(subroutine)을 일반화한 컴퓨터 프로그램 구성요소 이다
- 위키피디아 사전 정의
비선점형 멀티태스킹(Non-preemptive Multitasking)과 선점형 멀티태스킹(Preemptive Multitasking)
비선점형 멀티태스킹
- 하나의
Task
가Scheduler
로부터 CPU 사용권을 할당 받았을 때,Scheduler
가 강제로 CPU 사용권을 뺏는 것이 불가능하다.
선점형 멀티태스킹
- 비선점형 멀티태스킹과 반대로 CPU 사용권을 뺏을수 있다.
Coroutine
은 비선점형 멀티태스킹이고,Thread
는 선점형 멀티태스킹이다.
즉, Coroutine
은 병행성(Concurrency)은 제공하지만, 병렬성(Parallelism)은 제공하지 않는다.
Thread
는 잘 실행하다 OS에 의해 제어권이 뺏길수 있으나, (선점형)Coroutine
은 중단 지점을 만나지 않는 한 제어권을 양도하지 않고 이어서 계속 실행한다 (비선점형)
병행성과 병렬성
루틴(Routine
)이란 무엇인가
Routine
은 컴퓨터 프로그램에서 하나의 정리된 일이다.- 프로그램은 보통 크고 작은 여러가지
Routine
을 조합시킴으로써 성립한다.- 이
Routine
에서Main-Routine
과Sub-Routine
으로 나뉜다
- 이
- 위의 자바 코드를 보면,
main
은 말그대로Main 함수
이고,메인이 되는 함수
가 다른서브 함수
인plusOne
을 호출하는 형태이다.
Main Routine
과 Sub Routine
Main-Routine
은 프로그램 전체의 개괄적인 동작절차를 표시하도록 만들어진다.Sub-Routine
은 여러 명령어들을 모아 이름을 부여해 반복 호출할 수 있게 정의한 프로그램 구성요소로, 반복되는 특정 기능을 모아서 별도로 묶어놓은뒤 이름을 붙여놓은 것이다.- 따라서,
Sub-Routine
은 별도의 메모리에 해당 기능을 모아놓고,Sub-Routine
이 호출될 때마다,저장된 메모리
로 이동했다가, return을 통해원래 호출자의 위치
로 돌아오게 된다.
(다른 말로함수
라고 부르기도 한다)- 객체지향 언어에서는
메서드
도서브루틴
이라고 할 수 있다.
- 객체지향 언어에서는
SubRoutine
의 진입점과 탈출점
- 위의 자바코드 예시에서 볼 수 있듯이,
Sub-Routine
은Routine
진입점
과탈출점
이 명확하다.MainRoutine
이SubRoutine
을 호출하면,SubRoutine
의 맨 처음 부분에 진입하여, return문을 만나거나,SubRoutine
의 닫는 괄호를 만나면, 해당SubRoutine
을 탈출하게 된다.- 그리고 이
진입점
과탈출점
사이에Thread
는 Block되어 있다
- 어떤
Sub-Routine
에진입하는 방법
은 오직 1가지로, 해당 함수를 호출하면Sub-Routine
의 맨 처음부터 실행이 시작된다. 그때마다,활성 레코드(activation record)
라는 것이스택
에 할당되면서,Sub-Routine
내부의 로컬 변수 등이 초기화 된다. - 반면에,
Sub-Routine
안에서 여러 번 return을 사용할 수 있기 때문에,Sub-Routine
이 실행을 중단하고 제어를 호출한 쪽에게 돌려주는 지점은 여럿 있을 수 있다. - 다만, 일단
Sub-Routine
에서 반환되고 나면활성 레코드
가 스택에서 사라지기 때문에, 실행 중이던 모든 상태를 잃어버리게 된다.- 그래서
Sub-Routine
을 여러 번 반복 실행해도 전역변수나 다른 부수효과가 있지 않은 한 항상 결과를 반복해서 얻게된다.
- 그래서
Coroutine
의 개념
Co
+Routine
. 협력형 멀티태스킹을 프로그래밍 언어로 표현한 것이다.
(참고로Co-
접두어는 협력의 의미를 가짐)멀티태스킹
은 배경지식으로 언급했다시피 여러 작업을 동시에 수행하는 것처럼 보이거나, 실제로 동시에 수행하는 것이다.- 따라서 각 참여자들이 서로 자발적으로 협력해야만 비선점형 멀티태스킹이 제대로 작동할 수 잇다.
Coroutine
도Routine
의 일종이며, 서로 협력해서 실행을 주고받으면서 작동하는 여러Sub-Routine
을 말한다.- 즉,
Background Task
라는 점에서Coroutine
과Thread
가 비슷하게 느껴지지만,Coroutine
은 하나의 실행-종료 되어야하는 일(Job)이라고 한다면Thread
는 그 일이 실행되는 곳이다.
- 따라서,
Coroutine
은Thread
가 아니라 일반subrotuine
과 비슷한routine
이고,하나의 Thread
에 여러 개의coroutine
이 존재할 수 있다.
→ 결국Coroutine
실행 자체는 여전히Thread
상에서 동작하기에,Thread
의 대용체가 아니다. - 오히려 간편하면서 효율적으로 코드를 작성하기 위한 프로그래밍 테크닉에 가까우며 이것이
Coroutine
이 디자인된 본래 의미에 가깝다.
따라서
Coroutine
은Thread
안에서 실행되는일시 중단 가능한 작업의 단위
이다.
Coroutine
과 기존 Routine
과의 3가지의 차이점
Main
과Sub
의 개념이 존재 하지 않는다 → 모든routine
들이 서로를 호출 가능하다subroutine
처럼 꼭, return문이나, 마지막 닫는 괄호를 만나지 않더라도 언제든지 중간에 나갈수 있고, 언제든지 다시 나갔던 그 지점으로 들어올 수 있다.subRotuine
의 경우MainRoutine
에서특정 subRoutine
의 공간으로 이동한 후, return에 의해 호출자로 돌아와 다시 process를 진행한다.- 하지만,
coroutine
의 경우는routine
을 진행하는 중간에 멈추어 특정 위치로 돌아갔다가 다시 원래 위치로 돌아와나머지 routine
을 수행할 수 있다
subRoutine
은 진입점과 반환점이 단 1개 →MainRoutine
에 종속적이다coRoutine
은 진입점이 여러개 →MainRoutine
에 종속적 이지 않다.
- 따라서 대등하게 데이터를 주고 받을수 있다.
- 예를 들어,
Coroutine
의대표적인 generator
의 경우를 살펴보면어떤 함수 A
가 실행되다가Generator
인Coroutine B
를 호출하면A
가실행되던 스레드
안에서Coroutine B
의 실행이 시작된다Coroutine B
는 실행을 진행하다가- 실행을
A
에 양보한다 (yield
라는 명령을 사용하는 경우가 많다) A
는 다시Coroutine
을 호출했던 바로 다음 부터 실행을 계속 진행하다가- 또
Coroutine B
를 호출한다
→ 이때B
가일반적인 함수
라면,로컬 변수
를 초기화하면서 처음부터 실행을 다시 시작하겠지만,Coroutine
이면, 이전에yield
로 실행을 양보했던 지점부터 실행을 계속하게 된다.
따라서 Coroutine을 사용하는 경우 장점은, 일반적인 프로그램 로직을 기술하듯 코드를 작성하고
상대편 Coroutine에 데이터를 넘겨야 하는 부분에서만 yield를 사용하면 된다는 점이다.
- 또다른 예시로 이해하기
- 예시에서는
drawPerson
이라는 함수가 존재하고, 이 함수안에startCoroutine
이라는Coroutine Builder
가 있다. (이해를 위해 실제Coroutine Library
와 다른 방식으로 작성된 것) startCoroutine
이라는Coroutine
을 만나면, 해당 함수는Coroutine
으로 작동 가능하다.
→ 언제든 함수 실행 중간에 나갈 수 있고, 다시 들어올 수도 있는 자격이 부여됨
→suspend
로 선언된 함수를 만나면Coroutine
밖으로 잠시 나갈 수 있음- 실행 순서
Thread
의Main
함수가,drawPerson()
호출 →startCoroutine
Block을 만나,Coroutine
이 된다.(하나의Coroutine
을 만들어 시작)
→ 이제drawPerson
은 진입점과 탈출점이 여러 개가 된 자격이 주어진 것이다.Coroutine
이 실행되었어도,suspend
로 정의된 함수를 만나지 않는다면,
그냥 마지막 괄호를 만날 때 까지 계속 실행.suspend
로 정의된drawhead()
를 만나면, 그 아래 코드는 더이상 실행시키지 않고,drawPerson()
(Coroutine 함수)를 (잠시) 탈출한다.Main Thread
가 해당Coroutine
을 탈출하면, 다른 일을 하게된다.
(다른 코드를 실행하거나, UI를 처리하는 등)
여기서 중요한 점은,Head
는 어디에선가 계속해서 그려지고 있다는 점이다.drawHead()
함수의 기능은Main Thread
에서 병행성(동시성) 프로그래밍으로 작동할 수도 있고,다른 Thread
에서 돌아가고 있을수도 있다. (개발자가 자유롭게 선택 가능)drawHead()
는 2초가 걸리는 함수였다. 따라서Main Thread
가 다른 코드를 실행하다가도,drawHead()
가 기능을 다하면, 탈출했었던Coroutine
함수인drawPerson()
으로 다시 돌아온다.
그러고 나서, 그 아래에 있는drawBody()
부터 다시 재개(Resume) 된다.
Coroutine
의 동시성 이해하기
- 앞서 설명한대로,
동시성
(Concurrency)란, OS의Time-share
(시분할)에 의해 달성되는데,Thread
는 CPU Core에서 할당된Time Frame
안에서 일을 처리하는데, OS에 의해 선점될수도 있고, 제어권을 양도할수도 있다. - 반면,
Coroutine
은 OS가 아닌Thread
내에서다른 Coroutine
에 제어를 건네주기 때문에,Thread
내의모든 Coroutine
은 OS에서 관리하는 다른 Thread에 CPU Core를 양도하지 않고해당 Thread
의Time Frame
을 계속 이용할수 있다. - 즉,
Coroutine
은 OS가 아닌 사용자에 의해Time-Share
(시분할)을 달성한다고 생각하면 된다.Coroutine
은해당 Coroutine
을 실행하는Thread
에 할당된 동일한 Core에서 실행된다. Coroutine
의 동시성 처리는시분할
과 마찬가지로 병렬(Parallel)로 실행되는 것처럼 느껴지지만, 실제로는 실행이 겹치지 않고상호배치
(interleaved) 형태이다
→ 이때문에병행성
(동시성)은 지원하지만병렬성
은 지원하지 않는다 하는 것이다.- 기존의 효율적인 자원 활용을 위한
Multi-Thread
는 장점도 많지만, 공유 변수관리, DeadLock, Race Condition등 신경써야 하는 부분이 많은 점을Coroutine
을 통하여 쉽게 멀티태스킹을 구현할 수 있고, 앞서 설명한 점에 의해 Context Switching과 같은 비용이 발생하지 않는다.
예시로 이해하기
Main Thread
에 2개의Coroutine
이 있다고 할때,
가장 먼저 왼쪽Coroutine
,drawPersonToPaperA()
라는 함수를 만났을 경우,drawPersonToPaper()
가 호출되어,suspend
함수인drawHead()
를 만나게 되면, 이Coroutine
을 잠시 빠져 나간다.- 이때
Main Thread
가왼쪽 Coroutine
을 빠져나가서,다른 suspend 함수
를 찾거나,resume되어지는 다른 코드
를 찾는다. 이때,drawHead()
의delay(2000)
는Thread
를Block
시키지 않기 때문에, 다른 일들을 할 수 있다. 왼쪽 Coroutine
을 빠져나가,오른쪽 Coroutine
을 만나게 되면, 이제Coroutine A
와Coroutine B
를 아주 빠르게 왔다갔다 하면서 처리를 하게되고, 동시성(병행성) 프로그래밍이 되는 것이다.
→Thread
를 통해, 동시성 프로그래밍을 할 경우, CPU가 매번Thread
를 점유했다가, 놓아주는 과정을 반복해야 한다.하나의 Thread
에서 단순히 함수를 왔다갔다 하는 것과는 차원이 다른 비용이 든다
Coroutine의 효용성
효용성 조건
- 실행하려는 작업이 시간이 얼마 걸리지 않거나
- 실행하려는 작업이 I/O에 의한 대기 시간이 크고
- CPU 코어 수가 작아 동시에 실행할 수 있는 스레드 개수가 한정된 경우
- → 코루틴과 일반 스레드를 사용한 비동기 처리 사이에 차이가 커진다.
Kotlin에서의 Coroutine
여러 언어에서의 Coroutine
- 언어에 따라
generator
등 특정 형태의coroutine
만을 지원하는 경우도 있고,
좀 더 일반적인Coroutine
을 만들 수 있는 기능을 언어가 기본 제공하고,Generator
,asnc/await
등 다양한코루틴
은 그런 기본기능을 활용해 직접 사용자가 만들거나, 라이브러리를 통해 사용하는 형태가 있다. generator
만 제공하는 경우에도yield
시,future
등 비동기 처리가 가능한 객체를 넘기는 방법을 사용하면,async/await
등을 비교적 쉽게 구현할 수 있다.
Kotlin에선?
Kotlin
은 특정coroutine
은 언어가 지원하는 형태가 아닌,Coroutine
을 구현할 수 있는 기본 도구를 언어가 제공하는 형태이다- Kotlin의
Coroutine
지원 기본 기능들은,kotlin.coroutine
패키지 밑에 있고,
Kotlin 1.3부터는 Kotlin을 설치하면 별도의 설정없이도 모든 기능을 사용할 수 있다. - 하지만, Kotlin이 지원하는 기본 기능을 활용해 만든 다양한 형태의 코루틴들은,
kotlinx.coroutines
패키지 밑에 있다.
- Kotlin의
Kotlin
의Coroutine
은 suspend keyword로 marking된 함수를CPS
(Continuation Passing Style)로 변환하고, 이를Coroutine Builder
를 통해 적절한Thread
상에서 시나리오에 따라 동작하도록 구성된다.CPS 변환
은 프로그램의 실행 중 특정 시점 이후에 진행해야 하는 내용을 별도의 함수로 뽑고
(이런 함수를Continuation
이라 부른다), 그 함수에게 현재 시점까지 실행한 결과를 넘겨서 처리하게 만드는 소스코드 변환 기술이다.CPS
를 사용하는 경우 프로그램이 다음에 해야 할일이 항상Continuation
이라는 함수 형태로 전달되므로, 나중에 할일을 명확히 알 수 있고, 그Continuation
에 넘겨야 할 값이 무엇인지도 명확하게 알 수 있기 때문에 프로그램이 실행중이던 특정 시점의 맥락을 잘 저장했다가 필요할 때 다시 재개 할 수 있다.- 어떤 면에서
CPS
는Callback
스타일 프로그래밍과도 유사하다 CPS
를 사용하면Coroutine
을 만들기 위해 필수적인 일시중단 함수를 만드는 문제가 쉽게 해결될 수 있다. 다만, 모든 코드를 전부CPS
로만 변환하면 지나치게 많은 중간 함수들이 생길 수 있으므로,상태기계(State Machine)
을 적절히 사용해Coroutine
이 제어를 다른 함수에 넘겨야하는 시점에만Cotinuation
이 생기도록 만들 수 있다.
Coroutine
은 특정Thread
안에서 실행되더라도,Thread
와 묶이지 않는다는 점을 이해해야한다.-
Coroutine
의 일부를 특정Thread
에서 실행하고, 실행을 중지 한뒤, 나중에 다른Thread
에서 계속 실행하는 것이 가능하다. →Kotlin
이 실행 가능한thread
로coroutine
을 이동시키기 때문이다.- 따라서,
Coroutine
은 하나의Thread
에서 수행을 시작해도 수행 완료시점에서는 다른Thread
에서 끝날수 있다.
- 주의할 점은
suspend function
은Thread
와Schedule
의 관리를 수행하는 것이 아닌,
비동기 실행을 위한 중단(suspension) 지점의 정의라는 점이다.Coroutine
은 비선점형으로 동작하기 때문에, 실행 스케줄링이 OS에 의해 온전히 제어되는Thread
와는 다른 관점에서 봐야한다는 것이다.
- 각 언어별로
Coroutine
구현시에,Coroutine
내에자체 Stack 프레임
을 포함한 경우도 있고, 그렇지 않은 경우도 있다.Lua
와 같은 언어에서는Coroutine
에서 다른 함수를 호출해그 함수
에서Coroutine
의 실행을 중단할 수 있다 → 이는Coroutine
안에 자체적으로Stack 프레임
을 가지고 있어야 한다- Kotlin, C++, Javascript, Python 등과 같은 언어에서는
Coroutine
내에Stack
을 가지고 있지 않아서,Coroutine
에서하위 SubRoutine
의 실행을 중단시킬 수 없다.Coroutine Context
는Coroutine
중첩 호출을 통해서만 전달할수 있다.- 그러나 이러한
Stackless
의 이유로 훨씬 더 적은 메모리만으로 사용이 가능하다는 장점이 있다
- 그러나 이러한
참고
- Kotlin in Action 한국어판
반응형
'IT > Kotlin' 카테고리의 다른 글
[Kotlin/Coroutine] 4. Coroutine의 구성요소: Coroutine의 생성과 활용 (0) | 2023.04.22 |
---|---|
[Kotlin/Coroutine] 3. launch와 async의 Job과 Deferred (0) | 2023.04.07 |
[Kotlin/Coroutine] 2. Kotlin에서의 Coroutine: launch와 async (0) | 2023.03.24 |
[Kotlin] Delegate Pattern (0) | 2023.02.07 |
[Kotlin] Companion Object (+ Object, Object Declaration) (0) | 2023.01.24 |