개발자 노트

Coroutine 본문

카테고리 없음

Coroutine

jurogrammer 2022. 6. 17. 01:02

coroutine

코틀린도 모르면서 왜 이 주제를?

하도 주변에서 코루틴 코루틴해서 코루틴에 대해 알아보려고 합니다. 뭐 코틀린 루틴이여 뭐여~ 들었을 때 대화가 안되기도 하고… 듣다보니 어떻게 동작하는 지, 어떻게 사용할 수 있는지 궁금해졌네요.

뭐니? (wiki)

Coroutines are computer program components that generalize subroutines for non-preemptive multitasking, by allowing execution to be suspended and resumed.

 

코루틴들은 실행을 suspended, 및 resumed 를 허용하면서 non-preemptive 멀티태스킹 서브루틴을 일반화시킨 컴퓨터 프로그램의 구성요소들이다.

와… 이 정의만 하더라도 추론해볼 수 있는게 한 두개가 아니네요.

1. coroutines

co-routine

subroutines를 일반화시킨 컴퓨터 구성요소라고 합니다. 메인 루틴을 실행하는 과정에서 부수적인 일련의 명령어의 묶음을 실행할 때 이를 서브루틴이라고 부르잖아요?

예를 들어서 프로그램을 실행할 때 main 함수를 실행한 뒤 main 함수에서 일련의 함수들을 실행하죠. 이때 main 함수를 메인 루틴, main을 실행할 때 실행되는 일련의 함수들을 서브 루틴이라고 부를 수 있죠.

이 맥락의 routine이라는 표현은 특정 서비스를 수행하기 위한 일련의 동작을 의미하며, co-routine에서 co라는 접두사는 협력의 의미를 나타냅니다.

다시 말해서 coroutine이라는 뜻은 서로 협력하는 일련의 동작들을 의미한다고 볼 수 있습니다.

coroutine-s

잘~ 보면 복수형으로 표현하고 있습니다. 한 메인 루틴 내 서로 협력하는 루틴이 여러 개 일 수 있다는 것이죠.

2. Non-preemptive multitasking

운영체제를 공부하셨다면, preemptive non-preepmtive라는 용어를 곧바로 알아채릴 수 있을겁니다. cpu 스케줄링에서 preemptive 방식이라고 한다면, 프로세스가 모든 명령어를 실행하기도 전에 cpu 자원을 빼앗고 다른 프로세스에게 할당하는 방식을 의미합니다.

그리고 multitasking은 multi process와 유사하다고 보시면 됩니다. 다시 말해서 특정 기간 동안 여러가지 작업을 같이 수행하는 것입니다. multi-task. 즉, 여러 개의 일이 있다는 뜻이죠. 그리고 task는 특정 서비스를 의미한다고 유추해볼 수 있습니다.

따라서 ,여러 서비스간 작업을 수행하는데 각 서비스가 끝나는 것을 기다려준다. 그리고 나서 다른 서비스가 수행된다.라고 해석할 수 있죠.

3. execution to be suspended and resumed.

실행을 멈췄다가~ 다시 실행하기도 한다네요? 보통 메인 함수에서 다른 함수를 실행하면 그 함수를 싹 다 실행한 다음에야. 즉 return문까지 만나고 나서야 다시 main함수가 실행되는 형태인데,

다른 함수가 어느 정도 실행됬다가~ 멈추고 다시 그 부분부터 실행될 수 있다고 하네요? 독특합니다!

한편으로~ 프로세스를 생각해보면 특별한 것도 아니네요.

프로세스를 생각해보면 pcb에 pc를 저장하잖아요? 실행 도 중 preemptive되면 pcb에 pc를 저장한 다음, 다시 cpu서비스를 받을 때 pc부터 시작합니다. 오; 당연하게도 있던 개념이네요.

또 한편으로~ pc를 통해서 coroutine에 대해 어디까지 실행됬다 라는 정보도 저장되어야 함을 유추할 수 있습니다.

간단히 정리하자면, 서로 협력할 수 있는 루틴을 코루틴이라 한다. 라고 할 수 있겠습니다.

그리고 코틀린에 한정된 개념도 아니네요.

코틀린의 예제(kotlinlang)

코틀린 함수를 보기 앞서

코틀린은 expression을 함수에 대입하면 함수를 호출했을 때 해당 expression의 evaluate하고, 그 결과를 반환합니다. 다음 두 함수는 동치입니다.

fun hello() = print("Hello")
fun hello() {
    return print("Hello")
}

예제 코드

fun main() = runBlocking { // this: CoroutineScope
    launch { // launch a new coroutine and continue
        delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
        println("World!") // print after delay
    }
    println("Hello") // main coroutine continues while a previous one is delayed
}

동작

해당 코드는 Hello를 출력한 다음 1초 뒤 World!를 출력합니다. 어떻게 동작한 걸까요? 키워드를 알아보겠습니다.

launch

coroutine builder로, 새로운 coroutine을 실행시킵니다. CoroutineScope에서만 정의할 수 있다고 하네요.

delay

speicial suspending function 이라고 합니다.

특정한 시간동안 coroutinesuspend합니다.

Suspending은 thread를 block하지 않고 쓰레드 내 다른 코루틴이 실행하도록 합니다.

runBlocking

bridges 역할을 합니다. non-coroutine world인 main 함수와 runBlocking의 coroutine 실행블록끼리 연결시켜준다고 하네요.

runBlocking이라는 이름대로, 블록 내 coroutines이 모두 실행될 때 까지 runBlocking를 호출한 쓰레드는 blocking된다고 합니다. 여기선 main thread가 runBlocking을 호출하며, main thread는 blocking됩니다.

그럼 다시 맨 처음 배웠던 용어를 통하여 예제 코드를 설명 드리겠습니다.

  1. launch를 통해서 새로운 coroutine을 execution 합니다.
  2. 해당 코루틴이 처음 마주친 명령 문이 delay네요? deplay 함수는 해당 코루틴을 1초간 suspend 합니다.
  3. 그리고 Hello를 출력합니다.
  4. 1초 후에 해당 코루틴은 resume 합니다. World!를 출력하죠.

쓰레드의 동작 확인

정말~ main thread가 블록 되는지, 코루틴을 실행하는 쓰레드가 어떤 식으로 동작하는지 알기 위해 currentThread를 출력해보겠습니다.

import kotlinx.coroutines.delay
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking

fun main() {
    println("mainBlock thread id: " + Thread.currentThread().id)

    runBlocking { // this: CoroutineScope
        println("runBlock thread id: " + Thread.currentThread().id)

        launch { // launch a new coroutine and continue
            println("launchBlock thread id : " + Thread.currentThread().id)

            delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
            println("World!") // print after delay
        }
        println("Hello") // main coroutine continues while a previous one is delayed
    }
} 

출력 결과

mainBlock thread id: 1
runBlock thread id: 1
Hello
launchBlock thread id : 1
World!

알 수 있는 것

  1. 엥? mainThread가 block된다고 했는데 block되지 않네요? runBlocking을 그대로 실행하고 있습니다.
  2. 오… launch 블록에서 delay를 주었음에도, 쓰레드가 block되지 않고 곧바로 Hello를 실행하는 것을 알 수 있습니다. lanuch가 코루틴이 아니였다면, Thread를 새로 생성해야 했을 것이고, Thread.sleep 명령어로 thread를 blocking해주어야 했겠지요.

흠?

1.이 이상하네요. 코틀린 문서에서 다음과 같이 말하거든요

The name of runBlockingmeans that the thread that runs it (in this case — the main thread) gets blocked
for the duration of the call, until all the coroutines inside runBlocking { ... }complete their execution.

 

분명시 main thread가 블록된다고 되어 있는데요. 블록되지 않았습니다.

추측하자면, 결국 코틀린 내부적으로는 main 함수를 실행하는 것 조차 코루틴으로 보는 게 아닐까요? runBlocking을 실행하는 순간 suspended상태가 되는 것이죠. 이 이상은 나중에 생각해봐야 겠네요.

마무리

이번 글에선 간단하게 코루틴에 대해 알아 보았습니다.

정말 간단하게 표현하고자 하면 경량쓰레드라는 표현이 적절한 것 같습니다. 쓰레드를 생성하지 않고 루틴을 처리하니까요.

그리고 runBlocking처럼 석연찮은 부분도 있었지만 코루틴을 이해하기에는 큰 무리가 없었습니다.

욕심으로는 coroutine의 co 에 강조하여 서로 협동하는 예제를 구현하려 했지만 이 글이 너무 길어지네요.

꽤나 흥미로운 주제였습니다. 끝!

반응형
Comments