코틀린 코루틴(Coroutine) 기초

Aiden
7 min readFeb 8, 2020

코루틴이란?

Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing execution to be suspended and resumed (출처 : Wikipedia)

해석해 보자면, 코루틴이란 실행의 지연과 재개를 허용함으로서, 비 선점적 멀티태스킹을 위한 서브루틴을 일반화한 컴퓨터 프로그램 구성요소이다.

위 글을 이해하기 위해서는 크게 서브루틴과, 비 선점적 멀티태스킹 두가지 개념을 알고 가야한다

루틴과 서브루틴

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

루틴은 다시 메인 루틴과(main routine)과 서브루틴(subroutine)으로 나뉜다.메인 루틴은 프로그램 전체의 개괄적인 동작 절차를 표시하도록 만들어진다. 서브루틴은 반복되는 특정 기능을 모아 별도로 묶어 놓아 이름을 붙여 놓은 것이다. 서브루틴은 별도의 메모리에 해당 기능을 모아 놓고 있어, 서브루틴이 호출될 때마다 저장된 메모리로 이동했다가 return 을 통해 원래 호출자의 위치로 돌아오게된다. 서브루틴을 함수와 비슷한 개념이라고 생각하면 된다.

서브루틴(subroutine)과 코루틴(coroutine)

코루틴도 루틴의 일종이다. 다만 3가지의 차이점이 있는데

  1. 코루틴에서는 메인-서브의 개념이 없다. 모든 루틴들이 서로를 호출할 수 있다.
  2. 서브루틴의 경우에는 메인루틴에서 특정 서브루틴의 공간으로 이동한 후에 return 의해 호출자로 돌아와 다시 프로세스를 진행하는데 반해, 코루틴의 경우에는 루틴을 진행하는 중간에 멈추어서 특정 위치로 돌아갔다가 다시 원래 위치로 돌아와 나머지 루틴을 수행할 수 있다.
  3. 서브루틴은 진입점과 반환점이 단 하나밖에 없어 메인루틴에 종속적이지만, 코루틴은 진입지점이 여러개이기 때문에 메인루틴에 종속적이지 않아 대등하게 데이터를 주고 받을 수 있다는 특징이 있다.

비선점형 멀티태스킹(Non-preemptive Multitasking) 과 선점형 멀티태스킹(Preemptive Multitasking)

하나의 Task가 Scheduler로 부터 CPU 사용권을 할당 받았을 때, Scheduler가 강제로 CPU 사용권을 뺐을 수 없으면 비선점형 멀티태스킹이고, 뺐을 수 있으면 선점형 멀티태스킹이다.

코루틴은 비 선점형 멀티태스킹이고, 쓰레드는 선점형 멀티태스킹이다. 이 말은 즉 코루틴은 병행성(Concurrency)을 제공하지만 병렬성(Parallelism)을 제공하지 않는 다는 의미이다.

아니 그럼 병행성과 병렬성의 차이는 무엇인가?

병행성(Concurrency)

  1. 동시에 실행되는 것처럼 보이는 것.
  2. Logical Level에 속한다.
  3. Single Core 사용
  4. 물리적으로 병렬이 아닌 순차적으로 동작할 수 있다.
  5. 실제로는 Time-sharing으로 CPU를 나눠 사용함으로써 사용자가 Concurrency를 느낄 수 있도록 한다.

병렬성(Parallelism)

  1. 실제로 동시에 작업이 처리가 되는 것.
  2. Physical(Machine) Level에 속한다.
  3. 오직 Multi Core에서만 가능하다.

자 다시 돌아와서, 쓰레드에 비해 비 선점형 멀티태스킹인 코루틴의 장점은,

  1. 코루틴간의 작업 교환 때 비용이 적다는 것이다. 쓰레드간 작업 교환은 system call 또는 blocking call 콜 등의 비용이 발생한다.
  2. 동기화 작업을 위한 mutexes, semaphores 등의 장치가 필요없고
  3. OS의 지원도 필요가 없다

결론적으로 코루틴은 쓰레드에 비해 비용이 적은 멀티태스킹 방식이다.

코틀린 코루틴

코틀린에서는 2018년 10월 29일 Kotlin 1.3에 코틀린 코루틴이 추가되어 코루틴을 사용할 수 있게 되었다.

위의 예제에서 GlobalScope는 전체 어플리케이션의 라이프타임으로 scope로, launch는 해당 코드 블럭을 현재스레드를 blocking 하지 않고 코루틴으로 실행한다.

위의 예제에서 delay 함수는 blocking 할 수 없는 suspending 함수이기 때문에 GlobalScope.launch 대신 Thread를 사용하면, 에러가 발생한다.

그렇다면 blocking을 하기 위해서는 어떻게 해야할까?

runBlocking을 사용하면 된다.

coroutineScope 는 runBlocking 과 다르게, 모든 자식들이 완료될 때까지 현재 스레드를 블락시키지 않는다.

함수로 만들기 위해서는 suspend 키워드를 붙여서 만들면 된다.

코루틴 취소

  1. launch 블럭은 job Type을 리턴한다.
  2. job은 실행중인 코루틴을 취소할 수 있다.
  3. job.cancel()+job.join()은 job.cancelAndJoin()

suspend 함수

Result >

The answer is 42

Completed in 2017 ms

동시 처리를 원한다면 async 사용하면 된다.

Result >

The answer is 42

Completed in 1017 ms

async는

  1. 동시에 수행할 수 있다.
  2. Deferred<T> Type을 리턴한다.
  3. 같은 scope내에 하나라도 처리가 실패하면(throw exception) 모든 자식들이 다 처리 실패하는 문제가 있다.

Dispatchers

코루틴 실행에 사용하는 스레드를 결정한다. 모든 코루틴 빌더(ex, launch, async) 는 디스패처를 지정할 수 있다. 다음은 대표적인 Dispathers

  1. Default: 오래 걸리는 작업을 할 때. 공유된 백그라운드 스레드 풀 사용한다.
  2. IO: 파일을 쓰거나 API 콜 같은 상황에서 사용한다.
  3. Main: 메인스레드 작업에 사용한다.

withContext

스레드 간 점프에 사용한다. withContext를 사용함으로써 사용하는 쓰레드 변경 가능하다.

부모 코루틴의 책임

  1. 코루틴이 다른 코루틴의 coroutineScope 내에서 실행되면 CoroutineScope.coroutineContext를 통해 컨텍스트를 상속 받고부모 코루틴job 의 자식이 된다.
  2. 부모 코루틴이 취소되면, 자식 코루틴들은 재귀적으로 취소된다.
  3. GlobalScope는 부모 코루틴의 영향을 받지 않는다.
  4. 부모 코루틴은 자식 코루틴이 완료될때까지 대기한다. join()이 필요가 없다.

코루틴 스코프

메모리 릭을 피하기 위해서는, 액티비티가 파괴될 때(onDestroy에서) 코루틴을 취소해줘야 한다.(scope.cancel())

예외처리

  1. try/catch 사용하거나
  2. kotlin.runCatcing 사용한다 — 장점 : 코드가 깔끔해지고, 결과값을 받아서(Result Type) 처리할 수 있다.

참고자료

--

--

Aiden

안드로이드 개발자(개인 공부용도의 블로그)