IT/Kotlin

[Kotlin/Coroutine] 1. Coroutine이란 무엇인가

Hodie! 2023. 3. 16. 22:19
반응형

Coroutine을 사용하는 이유

비동기 프로그래밍을 사용하는 이유

  • 안드로이드에서 Main ThreadUI Thread이다.
  • 그렇기 때문에, Main Thread에서 UI를 그리는 것 이외에 복잡하고, 시간이 오래 걸리는 작업을 수행하는 것은 적합하지 않다.
    • 만약, 이러한 작업을 수행하게 될 경우, 해당 작업이 끝날 때까지 시스템은 사용자에게 보여질 UI를 그리지 못하게되고, 사용자는 앱을 시작했음에도 긴 시간동안 빈화면을 보게 되는 일이 발생한다.
      (UI Freezing)
    • 그래서 안드로이드에서는 해당 작업들을 Main Thread에서 하지 못하도록 한다.
    • 이러한 복잡한 작업의 에로는 네트워킹이 대표적이다. 만약 네트워크 작업을 Main Thread에서 진행하려고 할 경우, NetworkOnMainTrheadException이 발생하게 된다.
    • 이외에 내부 저장소 접근도 포함된다.

이러한 문제를 해결하기 위해 사용되는 것이 바로 비동기 프로그래밍이다.
Main Thread는 그대로 동작하면서, Main Thread 이외의 Background thread에서 작업을 수행하는 것이다.

비동기 작업을 수행하는 방식

이때, 비동기 작업을 수행하는 방식에 여러 가지가 존재하는데, 그중 가장 많이 사용되는 방식이 3가지이다.

  1. Callback
    1. 네트워크 작업을 Background Thread에서 동작시키고, 그 결과를 callback을 통해 전달한다
    2. 과거부터 비동기적 프로그래밍 방식에서 굉장히 많이 사용하는 방식이다.
    3. 이른바 "콜백 지옥" (Callback Hell) 이 펼쳐질 수 있다. 이 경우 코드 가독성이 굉장히 떨어지고, 유지보수가 어려워진다
  2. RxJava, RxKotlin
    1. indentation이 사라지기 때문에 Callback 방식보다 코드 가독성은 좋다
    2. 비동기 처리를 위해 굉장히 많이 사용하는 API
    3. 높은 효율성과 익숙해질 경우 여러 장점이 존재한다.
    4. 러닝커브가 굉장히 높다.
    5. Call이 누적될수록, Exception 발생 시, 영향 파악이 어려워지며, 모든 Transformation에 대한 파악도 어렵다는 단점이 잇다.
    6. 중간중간에 끼어있는 lambda block들은 동기적 코드에 비해 여전히 코드 가독성이 좋지않다.
  3. Coroutine
    1. Multi Threading 기법에서 CPU를 많이 소모하는 Context Switching이 적어 속도가 훨씬 빠르다
    2. CPU 자원을 좀 더 효율적으로 사용하기에 Light Weight Thread라는 표현을 사용하기도 한다.
      1. 다만, 추후에도 언급하지만 CoroutineThread가 아니라는 점 인지해야 한다.
      2. Thread안에서 실행될 수 있는 Procedure에 가깝다.
      3. 각 작업에 thread를 할당하는게 아니라 object를 할당하여 Context Switching을 최대한 줄이면서(Object Switching) DeadLock 문제도 자연스럽게 해결한다.
    3. Thread아무것도 하지 않는 상태를 거의 방지하여, Thread최대한 활용할 수 있다
    4. 동기코드 작성시와 동일한 가독성과 간결함을 유지할 수 있다.
    5. 러닝커브가 상대적으로 낮다.
      • Kotlin에 직접적으로 통합되어 있다.
      • Retrofit, Androidx 등 여러 라이브러리과 쉽게 통합이 되고, 유지관리가 좀 더 쉬워진다.

예시로 이해하기 (회원가입 완료 후 로그인 처리까지 하는 상황)

 

왜 Kotlin Coroutine 인가? suspend 함수, withContext

흔한 개발 시나리오 왜 Coroutine이 필요할까요? 이를 쉽게 이해하기 위해서 회원가입을 해야 하는 상황을 생각해봅시다. 편의를 위해서 회원가입 완료할 때 로그인 처리까지 하고 싶다고 합시다.

greedy0110.tistory.com

  • 처리 순서
    1. 사용자가 가입할 이메일과 비밀번호 입력
    2. 이메일, 비밀번호 기반으로 회원가입 요청
    3. 회원가입 성공후, 해당 이메일과 비밀번호를 사용해 로그인 요청
    4. 성공메시지 노출
  • 논리적으로 생각한 프로그램 코드
// 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

  • 이것을 우리는 그 악명높은 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 & LoopAsyncTask이였다.
    • 그러나, 안드로이드에서 Thread 프로그래밍의 위험성 때문에 AsyncTaskDeprecated시켰다.
    • 결국에, 안드로이드 개발자들에게 비동기 처리를 위한 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)

비선점형 멀티태스킹

  • 하나의 TaskScheduler로부터 CPU 사용권을 할당 받았을 때, Scheduler가 강제로 CPU 사용권을 뺏는 것이 불가능하다.

선점형 멀티태스킹

  • 비선점형 멀티태스킹과 반대로 CPU 사용권을 뺏을수 있다.

Coroutine은 비선점형 멀티태스킹이고, Thread는 선점형 멀티태스킹이다.

즉, Coroutine병행성(Concurrency)은 제공하지만, 병렬성(Parallelism)은 제공하지 않는다.

  • Thread는 잘 실행하다 OS에 의해 제어권이 뺏길수 있으나, (선점형)
    Coroutine중단 지점을 만나지 않는 한 제어권을 양도하지 않고 이어서 계속 실행한다 (비선점형)

병행성과 병렬성

루틴(Routine)이란 무엇인가

  • Routine은 컴퓨터 프로그램에서 하나의 정리된 일이다.
  • 프로그램은 보통 크고 작은 여러가지 Routine을 조합시킴으로써 성립한다.
    • Routine에서 Main-RoutineSub-Routine으로 나뉜다

출처: https://wooooooak.github.io/kotlin/2019/08/25/코틀린-코루틴-개념-익히기/

  • 위의 자바 코드를 보면, main은 말그대로 Main 함수이고, 메인이 되는 함수가 다른 서브 함수plusOne을 호출하는 형태이다.

Main RoutineSub Routine

  • Main-Routine은 프로그램 전체의 개괄적인 동작절차를 표시하도록 만들어진다.
  • Sub-Routine여러 명령어들을 모아 이름을 부여해 반복 호출할 수 있게 정의한 프로그램 구성요소로, 반복되는 특정 기능을 모아서 별도로 묶어놓은뒤 이름을 붙여놓은 것이다.
  • 따라서, Sub-Routine은 별도의 메모리에 해당 기능을 모아놓고, Sub-Routine이 호출될 때마다, 저장된 메모리로 이동했다가, return을 통해 원래 호출자의 위치로 돌아오게 된다.
    (다른 말로 함수라고 부르기도 한다)
    • 객체지향 언어에서는 메서드서브루틴이라고 할 수 있다.

SubRoutine의 진입점과 탈출점

  • 위의 자바코드 예시에서 볼 수 있듯이, Sub-RoutineRoutine 진입점탈출점이 명확하다.
    • MainRoutineSubRoutine을 호출하면, 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는 그 일이 실행되는 곳이다.
  • 따라서, CoroutineThread가 아니라 일반 subrotuine과 비슷한 routine이고,
    하나의 Thread에 여러 개의 coroutine이 존재할 수 있다.
    → 결국 Coroutine 실행 자체는 여전히 Thread 상에서 동작하기에, Thread의 대용체가 아니다.
  • 오히려 간편하면서 효율적으로 코드를 작성하기 위한 프로그래밍 테크닉에 가까우며 이것이 Coroutine이 디자인된 본래 의미에 가깝다.

따라서 CoroutineThread 안에서 실행되는 일시 중단 가능한 작업의 단위이다.

Coroutine기존 Routine 과의 3가지의 차이점

    1. Main Sub의 개념이 존재 하지 않는다모든 routine들이 서로를 호출 가능하다
    2. subroutine 처럼 꼭, return문이나, 마지막 닫는 괄호를 만나지 않더라도 언제든지 중간에 나갈수 있고, 언제든지 다시 나갔던 그 지점으로 들어올 수 있다.
      • subRotuine의 경우 MainRoutine에서 특정 subRoutine공간으로 이동한 후, return에 의해 호출자로 돌아와 다시 process를 진행한다.
      • 하지만, coroutine의 경우는 routine을 진행하는 중간에 멈추어 특정 위치로 돌아갔다가 다시 원래 위치로 돌아와 나머지 routine을 수행할 수 있다
    3. subRoutine은 진입점과 반환점이 단 1개MainRoutine에 종속적이다
      coRoutine진입점이 여러개MainRoutine에 종속적 이지 않다.
      • 따라서 대등하게 데이터를 주고 받을수 있다.
  • 예를 들어, Coroutine대표적인 generator의 경우를 살펴보면
    1. 어떤 함수 A가 실행되다가 GeneratorCoroutine B호출하면
    2. A실행되던 스레드 안에서 Coroutine B실행이 시작된다
    3. Coroutine B는 실행을 진행하다가
    4. 실행을 A양보한다 (yield라는 명령을 사용하는 경우가 많다)
    5. A는 다시 Coroutine호출했던 바로 다음 부터 실행을 계속 진행하다가
    6. Coroutine B호출한다
      → 이때 B일반적인 함수라면, 로컬 변수초기화하면서 처음부터 실행을 다시 시작하겠지만, Coroutine이면, 이전에 yield실행을 양보했던 지점부터 실행을 계속하게 된다.
따라서 Coroutine을 사용하는 경우 장점은, 일반적인 프로그램 로직을 기술하듯 코드를 작성하고
상대편 Coroutine에 데이터를 넘겨야 하는 부분에서만 yield를 사용하면 된다는 점이다.
  • 또다른 예시로 이해하기

  • 예시에서는 drawPerson이라는 함수가 존재하고, 이 함수안에 startCoroutine이라는 Coroutine Builder가 있다. (이해를 위해 실제 Coroutine Library와 다른 방식으로 작성된 것)
  • startCoroutine이라는 Coroutine을 만나면, 해당 함수는 Coroutine으로 작동 가능하다.
    → 언제든 함수 실행 중간에 나갈 수 있고, 다시 들어올 수도 있는 자격이 부여됨
     suspend로 선언된 함수를 만나면 Coroutine 밖으로 잠시 나갈 수 있음
  • 실행 순서
    1. ThreadMain 함수가, drawPerson()호출 → startCoroutine Block을 만나, Coroutine이 된다.(하나의 Coroutine을 만들어 시작)
      → 이제 drawPerson은 진입점과 탈출점이 여러 개가 된 자격이 주어진 것이다.
    2. Coroutine이 실행되었어도, suspend로 정의된 함수를 만나지 않는다면,
      그냥 마지막 괄호를 만날 때 까지 계속 실행.
    3. suspend로 정의된 drawhead()를 만나면, 그 아래 코드는 더이상 실행시키지 않고, drawPerson()(Coroutine 함수)를 (잠시) 탈출한다.
    4. Main Thread가 해당 Coroutine을 탈출하면, 다른 일을 하게된다.
      (다른 코드를 실행하거나, UI를 처리하는 등)
      여기서 중요한 점은, Head는 어디에선가 계속해서 그려지고 있다는 점이다.
    5. drawHead() 함수의 기능은 Main Thread에서 병행성(동시성) 프로그래밍으로 작동할 수도 있고, 다른 Thread에서 돌아가고 있을수도 있다. (개발자가 자유롭게 선택 가능)
    6. drawHead()는 2초가 걸리는 함수였다. 따라서 Main Thread가 다른 코드를 실행하다가도, drawHead()가 기능을 다하면, 탈출했었던 Coroutine 함수인 drawPerson()으로 다시 돌아온다.
      그러고 나서, 그 아래에 있는 drawBody()부터 다시 재개(Resume) 된다.

Coroutine의 동시성 이해하기

  • 앞서 설명한대로, 동시성(Concurrency)란, OSTime-share(시분할)에 의해 달성되는데, Thread는 CPU Core에서 할당된 Time Frame 안에서 일을 처리하는데, OS에 의해 선점될수도 있고, 제어권을 양도할수도 있다.
  • 반면, CoroutineOS가 아닌 Thread내에서 다른 Coroutine에 제어를 건네주기 때문에,
    Thread내의 모든 Coroutine은 OS에서 관리하는 다른 Thread에 CPU Core를 양도하지 않고 해당 ThreadTime Frame계속 이용할수 있다.
  • 즉, Coroutine은 OS가 아닌 사용자에 의해 Time-Share(시분할)을 달성한다고 생각하면 된다. Coroutine해당 Coroutine을 실행하는 Thread에 할당된 동일한 Core에서 실행된다.
  • Coroutine동시성 처리시분할과 마찬가지로 병렬(Parallel)로 실행되는 것처럼 느껴지지만, 실제로는 실행이 겹치지 않고 상호배치(interleaved) 형태이다
    → 이때문에 병행성(동시성)은 지원하지만 병렬성은 지원하지 않는다 하는 것이다.
  • 기존의 효율적인 자원 활용을 위한 Multi-Thread는 장점도 많지만, 공유 변수관리, DeadLock, Race Condition등 신경써야 하는 부분이 많은 점을 Coroutine을 통하여 쉽게 멀티태스킹을 구현할 수 있고, 앞서 설명한 점에 의해 Context Switching과 같은 비용이 발생하지 않는다.

예시로 이해하기

 

코틀린 코루틴(coroutine) 개념 익히기 · 쾌락코딩

코틀린 코루틴(coroutine) 개념 익히기 25 Aug 2019 | coroutine study 앞서 코루틴을 이해하기 위한 두 번의 발악이 있었지만, 이번에는 더 원론적인 코루틴에 대해서 알아보려 한다. 코루틴의 개념이 정확

wooooooak.github.io

  1. Main Thread에 2개의 Coroutine이 있다고 할때,
    가장 먼저 왼쪽 Coroutine, drawPersonToPaperA()라는 함수를 만났을 경우, drawPersonToPaper()가 호출되어, suspend 함수인 drawHead()를 만나게 되면, 이 Coroutine을 잠시 빠져 나간다.
  2. 이때 Main Thread왼쪽 Coroutine을 빠져나가서, 다른 suspend 함수를 찾거나, resume되어지는 다른 코드를 찾는다. 이때, drawHead()delay(2000)ThreadBlock시키지 않기 때문에, 다른 일들을 할 수 있다.
  3. 왼쪽 Coroutine을 빠져나가, 오른쪽 Coroutine을 만나게 되면, 이제 Coroutine ACoroutine B를 아주 빠르게 왔다갔다 하면서 처리를 하게되고, 동시성(병행성) 프로그래밍이 되는 것이다.
    Thread를 통해, 동시성 프로그래밍을 할 경우, CPU가 매번 Thread를 점유했다가, 놓아주는 과정을 반복해야 한다. 하나의 Thread에서 단순히 함수를 왔다갔다 하는 것과는 차원이 다른 비용이 든다

Coroutine의 효용성

효용성 조건

  1. 실행하려는 작업이 시간이 얼마 걸리지 않거나
  2. 실행하려는 작업이 I/O에 의한 대기 시간이 크고
  3. CPU 코어 수가 작아 동시에 실행할 수 있는 스레드 개수가 한정된 경우
  4. → 코루틴과 일반 스레드를 사용한 비동기 처리 사이에 차이가 커진다.

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 패키지 밑에 있다.
  • KotlinCoroutinesuspend keyword로 marking된 함수를 CPS(Continuation Passing Style)로 변환하고, 이를 Coroutine Builder를 통해 적절한 Thread상에서 시나리오에 따라 동작하도록 구성된다.
    • CPS 변환은 프로그램의 실행 중 특정 시점 이후에 진행해야 하는 내용을 별도의 함수로 뽑고
      (이런 함수를 Continuation이라 부른다), 그 함수에게 현재 시점까지 실행한 결과를 넘겨서 처리하게 만드는 소스코드 변환 기술이다.
    • CPS를 사용하는 경우 프로그램이 다음에 해야 할일이 항상 Continuation이라는 함수 형태로 전달되므로, 나중에 할일을 명확히 알 수 있고, 그 Continuation넘겨야 할 값이 무엇인지도 명확하게 알 수 있기 때문에 프로그램이 실행중이던 특정 시점의 맥락을 잘 저장했다가 필요할 때 다시 재개 할 수 있다.
    • 어떤 면에서 CPSCallback 스타일 프로그래밍과도 유사하다
    • CPS를 사용하면 Coroutine을 만들기 위해 필수적인 일시중단 함수를 만드는 문제가 쉽게 해결될 수 있다. 다만, 모든 코드를 전부 CPS로만 변환하면 지나치게 많은 중간 함수들이 생길 수 있으므로, 상태기계(State Machine)을 적절히 사용해 Coroutine이 제어를 다른 함수에 넘겨야하는 시점에만 Cotinuation이 생기도록 만들 수 있다.
  • Coroutine은 특정 Thread안에서 실행되더라도, Thread와 묶이지 않는다는 점을 이해해야한다.
    • Coroutine의 일부를 특정 Thread에서 실행하고, 실행을 중지 한뒤, 나중에 다른 Thread에서 계속 실행하는 것이 가능하다. → Kotlin실행 가능한 thread coroutine을 이동시키기 때문이다.
    • 따라서, Coroutine 하나의 Thread에서 수행을 시작해도 수행 완료시점에서는 다른 Thread에서 끝날수 있다.
  • 주의할 점은 suspend functionThreadSchedule관리를 수행하는 것이 아닌,
    비동기 실행을 위한 중단(suspension) 지점의 정의라는 점이다.
    • Coroutine비선점형으로 동작하기 때문에, 실행 스케줄링이 OS에 의해 온전히 제어되는 Thread와는 다른 관점에서 봐야한다는 것이다.
  • 각 언어별로 Coroutine 구현시에, Coroutine 내에 자체 Stack 프레임을 포함한 경우도 있고, 그렇지 않은 경우도 있다.
    • Lua와 같은 언어에서는 Coroutine에서 다른 함수를 호출그 함수에서 Coroutine의 실행을 중단할 수 있다 → 이는 Coroutine안에 자체적으로 Stack 프레임을 가지고 있어야 한다
    • Kotlin, C++, Javascript, Python 등과 같은 언어에서는 Coroutine 내에 Stack가지고 있지 않아서, Coroutine에서 하위 SubRoutine실행을 중단시킬 수 없다. Coroutine ContextCoroutine 중첩 호출을 통해서만 전달할수 있다.
      • 그러나 이러한 Stackless의 이유로 훨씬 더 적은 메모리만으로 사용이 가능하다는 장점이 있다

참고

  1. Kotlin in Action 한국어판
 

코틀린 코루틴(coroutine) 개념 익히기 · 쾌락코딩

코틀린 코루틴(coroutine) 개념 익히기 25 Aug 2019 | coroutine study 앞서 코루틴을 이해하기 위한 두 번의 발악이 있었지만, 이번에는 더 원론적인 코루틴에 대해서 알아보려 한다. 코루틴의 개념이 정확

wooooooak.github.io

 

왜 Kotlin Coroutine 인가? suspend 함수, withContext

흔한 개발 시나리오 왜 Coroutine이 필요할까요? 이를 쉽게 이해하기 위해서 회원가입을 해야 하는 상황을 생각해봅시다. 편의를 위해서 회원가입 완료할 때 로그인 처리까지 하고 싶다고 합시다.

greedy0110.tistory.com

 

반응형