brunch

You can make anything
by writing

C.S.Lewis

by 서준수 Aug 21. 2023

코루틴 delay 함수

코틀린 코루틴 (3)

코루틴 delay 함수


처음에 코루틴을 다음과 같이 정의했다. 코루틴은 특정한 스레드에 종속되지 않고 일시정지를 통해 동시성을 제공한다. 그러면 일시정지는 어떻게 할 수 있을까? delay 함수(이하 delay())를 사용하면 된다.


delay()는 지정한 시간만큼 코루틴을 일시정지 시킨다. 정지가 아니고 일시정지인만큼 해당 코루틴은 지정한 시간이 지난 후에 다시 실행된다.


delay()가 코루틴을 일시정지 시키는 역할을 하기 때문에 다음과 같이 CoroutineScope 내에서 호출되어야 한다.

위 코드의 결과는 어떻게 될까? 일단 Hello, 가 출력된다. 그 후 1초 동안 코루틴이 일시정지 된다. 따라서 1초 동안 아무 작업이 진행되지 않는다. 1초 후 World! 가 출력된다.

간단한 내용이지만 그림으로 표현하면 위와 같다. 앞으로 좀 더 복잡한 동작을 이해할 때 그림을 사용하면 도움이 될 것이라 생각된다.


delay()가 코루틴을 일시정지 한다는 것까지는 쉽게 알 수 있었을 것이다. 그러면 일시정지 되어 있는 동안 아무런 작업을 하지 않고 대기만 할까? 아니다. 스레드 자체를 일시정지하는 것이 아니라 코루틴만 일시정지한다는 것에 주목해야 한다. 따라서 delay()를 통해 일시정지 하면 해당 코루틴은 잠시 멈추지만 다른 코루틴은 해당 스레드에서 동작할 수 있다.


새로운 코루틴을 만들 수 있는 코루틴 빌더인 launch의 주요 특징이 동시에 코드가 실행되도록 하는 것이라고 했다. 그러한 동시성은 비동기적인 동작으로 이룬다고 했다. delay()를 알게 되었기 때문에 이 말이 무슨 말인지 일부분은 눈으로 확인할 수 있는 준비가 되었다. (일부분이라고 한 것을 기억하자.)


먼저 delay()를 사용하지 않고 launch가 두 개인 경우를 보자.

runBlocking과 launch의 실행 순서는 앞서 살펴보았다. 여기서 문제는 launch가 두 개라는 점이다. 현재 launch는 runBlocking 내부에 존재하기 때문에 실행 순서가 선언한 순서대로 실행된다.


왜냐하면 runBlocking 내부에 존재한다는 것은 runBlocking이 붙잡고 있는 메인 스레드, 즉 동일한 스레드 내에서 동작하는 코루틴이기 때문이다. 하지만 코루틴은 특정한 스레드에 종속되지 않는다고 했다. 만약 다른 스레드(또는 스레드풀)에서 실행되는 launch가 두 개였다면 실행 순서를 보장할 수 없다. (지금은 이 내용이 이해되지 않으면 그냥 넘어가면 된다. 추후 Dispatcher라는 개념을 다룰 때 다시 이야기하게 될 것이기 때문이다.)


주의할 점은 runBlocking 내부의 launch도 실행이 순차적이라는 것이지 완료는 순차적이지 않을 수 있다는 것이다.


아무튼 현재 상황에서는 실행 순서가 보장되었고 실행결과도 다음과 같이 순차적인 경우가 많다.

Hello World!
launch A Start
launch A End
launch B Start
launch B End


자, 그러면 여기서 delay()를 추가해서 첫 번째 launch를 잠시 멈춰보자.

실행결과가 예측되는가? 코루틴을 아직 잘 모르는 상태라면 이렇게 오해할 수 있다.

Hello World!
launch A Start
(1초 정지)
launch A End
launch B Start
launch B End

하지만 조금 전에 delay()를 통해 코루틴을 일시정지 하면 해당 코루틴은 잠시 멈추지만 다른 코루틴은 해당 스레드에서 동작할 수 있다고 했다. 이 말은 다음과 같은 그림으로 표현할 수 있다.

편의상 첫 번째 launch를 launch A라고 하고 두 번째 launch를 launch B라고 하자. launch A에서 launch A Start를 출력한 후에 delay()로 인해 1초 일시정지 된다. 이때 스레드는 여전히 사용 가능한 자원이다. 따라서 해당 스레드 내에 존재하는 다음 코루틴인 launch B가 동작한다. launch B는 2개의 출력문을 수행하는 단순한 작업이기 때문에 1초 이내에 실행될 것이다. 그러므로 launch B Start와 launch B End 모두 출력된다. 만약 이 동작까지 완료하는데 100ms를 사용했다면 900ms 후에 일시정지가 끝나서 launch A End가 출력될 것이다. 그래서 실행결과는 다음과 같다.

Hello World!
launch A Start
launch B Start
launch B End
launch A End

이 부분이 바로 비동기와 관련이 있다. launch A의 코드 블록이 완료되는 것을 기다리지 않고 delay()를 통해서 제어권을 launch B로 넘겼다. 동기적이라면 처음의 오해처럼 launch A의 제어권이 넘어가지 않고 단순히 1초 정지 후 나머지 launch A의 모든 작업을 수행한 후에 launch B가 실행되어야 한다.


여기까지 별 어려움이 없었다면 다음과 같은 실행결과를 얻으려면 코드를 어떻게 수정해야 할까?

Hello World!
launch A Start
launch B Start
launch A End
launch B End

launch A를 실행한 후 일시정지한 후 launch B를 실행하고 다시 launch A로 제어권이 넘어가야 한다. launch B에서 launch A로 제어권을 넘기려면 launch B를 일시정지 하면 된다.

launch B의 delay를 1초 보다 조금 더 길게 주면 의도가 더욱 명확해진다. 다만 launch A와 동일하게 1초로 준다고 하더라고 launch A가 먼저 실행되는 만큼 찰나의 차이가 존재하기 때문에 의도한 동작을 제대로 수행한다. (그 찰나의 시간을 정확히 알 수 있다면 1초 미만으로 줄 수도 있다. 대충 999ms를 지정하고 실행해도 된다.)

그림으로 그려보면 위와 같다.


delay()는 launch 내에서만 동작하는 것이 아님을 알 것이다. 코루틴을 일시정지 시키는 역할을 하기 때문에 CoroutineScope 내에서 호출되어야 한다고 했기 때문이다. 이 말은 runBlocking 내에서도 delay()를 사용할 수 있다는 것이다. 이것을 염두에 두고 다음의 출력결과를 얻을 수 있는 코드를 작성해 보라.

launch A Start
launch B Start
Hello World!
launch A End
launch B End

이러한 다양한 상황에 대해서 고민을 해보는 것이 delay()의 동작을 이해하는데 많은 도움이 된다. 바로 코드 작성하는 것이 어렵다면 여러 가지 delay() 예제를 보고 그 결과를 추측하는 연습을 먼저 해도 좋다.


요약하자.

- 코루틴의 delay 함수는 지정한 시간만큼 코루틴을 일시정지 한다.
- 일시정지는 완전한 정지가 아니기 때문에 지정한 시간 이후 다시 실행된다.
- 일시정지 하면 다른 코루틴 실행이 가능하다.
- 일시정지 후 다른 코루틴 실행이 가능하다는 것은 제어권이 넘어가는 것이고 이는 비동기이다.
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari