코루틴 관련 포스팅은 아래의 포스팅들을 같이 순차적으로 참고하시면 좋습니다. 클릭하면 해당 포스팅으로 이동합니다.
1. 코루틴 기본 개념
2. 코루틴 빌더
3. Job과 코루틴의 라이프 사이클
4. 코루틴 컨텍스트와 스코프
5. Flow 기본 개념
6. Flow 핵심 API 정복하기
7. Flow 제어하기 (예외, 완료 등)
코루틴이란?
코루틴은 협력을 의미하는 "co"와 routine이 합쳐진 말로, 말 그대로 협력하는 루틴이라는 뜻을 가지고 있다.
그렇다면 일반적으로 프로그래밍 세계에서 루틴은 무엇을 의미할까? 어떤 프로그램이 실행될 때 불려지거나 반복해서 사용되도록 만들어진 일련의 코드들을 지칭하는 용어라고 한다. 쉽게 그냥 함수라고 말할 수 있다.
이에 확장해서 서브루틴이라는 개념도 있는데, 루틴 안에 존재하는 다른 루틴을 서브루틴이라고 부른다. Java나 Kotlin으로 따지면, `main` 함수 안에 다른 함수가 호출되는 경우 여기서 다른 함수는 서브루틴이라고 볼 수 있다.
그렇다면 루틴과 서브루틴, 코루틴의 차이는 뭘까? 코드로 한번 살펴보자.
fun main() {
log("start")
doSomething(1)
doSomething(2)
log("end")
}
fun doSomething(num: Int) {
log("do something $num")
}
fun log(message: String) = println("[${Thread.currentThread().name} $message]")
위 코드의 `main`이라는 함수(루틴)는 start를 출력하고, `doSomething` 함수(서브루틴)을 두 번 호출한 후 end를 출력한다. 실제 메인 함수를 실행시키면 어떻게 출력될까? 예상하는 그대로이다.
[main] start
[main] do something 1
[main] do something 2
[main] end
루틴과 서브루틴은 실행 결과를 보면 코드를 작성한 그대로 순차적으로 실행이 된다.
그림으로 표현하면 위와 같을 것이다.
그렇다면 코루틴은 뭐가 다르기에 협력하는 루틴이라고 부르는 걸까? 한번 코루틴을 사용해서 코드를 살짝 바꿔보자.
fun main() = runBlocking {
log("start")
launch { doSomething(1) }
yield()
launch { doSomething(2) }
log("complete")
}
fun doSomething(num: Int) {
log("do something $num")
}
fun log(message: String) = println("[${Thread.currentThread().name} $message]")
이전 코드랑 동일한데, 코루틴을 사용하도록 변경했다. 간단하게 `runBlocking`은 코루틴을 생성하는 함수라고 생각하면 되고, `launch` 역시 새로운 코루틴을 생성하는 함수라고 생각하면 된다.
즉, 위 코드에서는 3개의 코루틴을 생성하였으며 두 번째 코루틴을 생성한 후 루틴을 양보하는 의미의 `yield` 함수를 사용했다.
[main @coroutine#1] start
[main @coroutine#2] do something 1
[main @coroutine#1] complete
[main @coroutine#3] do something 2
실행 결과는 위와 같다. 분명 루틴의 순서는 같은데, 코루틴을 사용하니 결과는 달라졌다.
위 그림처럼 코루틴은 루틴이 순서대로 실행되지 않는다. 하나의 스레드에 여러 코루틴이 서로 협력하며 실행되게 된다.
중간에 `yield`를 호출하게 되면, 기존에 실행중이던 코루틴(1)은 잠시 중단되고 다음 코루틴(2)을 실행하게 된다. 그리고 다음 코루틴(2)이 종료되면, 다시 원래 실행중이던 코루틴(1)으로 돌아간다. 그리고 원래 코루틴(1)이 종료되고 다음 코루틴(3)을 실행하게 된 것이다.
이처럼 코루틴은 일반적인 루틴과는 다르게 중단 및 재개할 수 있다. 이와 같은 특성 때문에 루틴끼리 협력을 한다고 표현하며 코루틴이라는 이름으로 부르게 된 것이다.
코루틴과 스레드
위 예시처럼 여러 코루틴을 동시에 실행했다가 중단하고 재개하는 과정은 마치 멀티 스레드 프로그래밍처럼 보일 수 있다. 하지만 다르다. 실제 위 예시에서도 스레드는 main 스레드 하나만 사용했었다.
보통 멀티 스레드 프로그래밍은 하나의 프로세스에서 스레드를 여러개 사용하여 스레드간 컨텍스트 스위칭(Context Swithching)을 통해 동시에 실행되는 것처럼 만드는 것을 얘기한다.
반면 코루틴은 하나의 스레드에서 여러 개의 코루틴을 실행하는 방식이다. 동시에 실행하는 것은 아니고, 코루틴간 재개와 중단을 통해 협력하여 하나의 스레드에서 하나의 루틴만을 실행하는 것이 아니라 여러개의 루틴을 수행할 수 있다는 말이다. 마치 하나의 프로세스에서 여러 스레드를 사용하는 것처럼, 하나의 스레드에서 여러 코루틴을 사용한다고 생각하면 된다.
그림으로 표현하면 위와 같을 것이다. 결론적으로는 둘 다 컨텍스트 스위칭이 발생하긴 하는데, 발생하는 메모리와 시간 등의 복합적인 비용 측면에서 코루틴 간의 스위칭이 더 적게 든다. (코루틴의 컨텍스트 스위칭 관련해서는 해당 글 참조)
또한 하나의 스레드 에서 여러 코루틴을 실행시킬 수 있게 때문에 하나의 코루틴이 중단된 경우에도 스레드는 일시정지되지 않는다. 만약 작업이 총 3개가 있고, 작업1은 작업2의 결과물이 필요하다고 해보자.
스레드의 경우 위와 같은 방식으로 진행될 것이다. 작업2가 완료될 때 까지 스레드는 일시정지된다.
반면 코루틴은 코루틴1이 중단될 경우 다른 코루틴이 해당 스레드에서 작업을 수행한다. 이후 코루틴2의 결과가 나올 경우 다시 코루틴1을 재개하게 된다.
또한 코루틴은 기본적으로 비선점형 방식의 멀티태스킹을 사용하기 때문에, 실행중인 코루틴을 잠시 중단하거나, 다른 코루틴으로 양보하지 않는 이상 OS의 개입 없이 한번 실행된 코루틴은 계속해서 CPU를 점유하게 된다. 이는 스케줄러에 의해 실행 시점이 정해지지 않고, 사용자 수준에서 코루틴의 실행시점이 정해지며 스케줄러의 호출 빈도가 낮아지기 때문에 선점형 멀티태스킹을 사용하는 스레드보다 효율적이다.
다음 포스팅에서 이어집니다.