[OS, Kotlin] 운영체제와 함께 이해해 보는 코틀린 코루틴의 동작 방식 (1)

Kame·2025년 3월 24일

OS

목록 보기
1/3
post-thumbnail

들어가며

이 글에서는 상향식 접근을 토대로 코루틴의 협력 방식을 알아봅니다.

Kotlin Coroutines에서는 각 루틴이 협력적(cooperative)으로 동작합니다.
그 동작 원리를 이해하기 위해 운영체제의 주요 개념들을 알아보도록 하겠습니다.

선행 지식


동시성(Concurrency)

여러 계산을 동시에 수행하는 시스템의 특성 (출처 : Wikipedia)

필요성

컴퓨팅 자원을 어떻게 하면 효율적으로 사용할 수 있을까?

😂 먼저 알고 갑시다
프로그램을 실행시키기 위해서는 CPU와 메인 메모리가 필요하다.
프로세스는 프로그램이 메모리에 적재된 상태(= 프로그램의 실행 인스턴스)이다.

초창기 컴퓨팅 시스템처럼, 단일 CPU에 단일 코어만 존재한다고 가정해 보겠습니다.

단일 프로세스 시스템에서 하나의 CPU는 한 순간에 하나의 프로세스만 실행시킬 수 있습니다. 프로세스가 실행 중 I/O 작업에 들어가게 되면 CPU는 유휴(idle) 상태에 들어가는데, 이 상태에서 CPU는 다른 프로세스를 실행할 수 없습니다. 결국 CPU가 작업을 수행하지 못하는 시간이 발생하며, CPU 사용률은 크게 저하될 것입니다.

CPU는 컴퓨터 시스템에서 가장 중요한 자원으로, CPU의 성능은 시스템의 처리 속도와 효율성, 사용성 등으로 직결됩니다. 따라서 CPU를 더욱 효율적으로 사용할 수 있는 방법이 필요합니다.

이 때 활용해 볼 수 있는 개념이 동시성입니다. 동시성을 활용하면 여러 프로그램을 메모리에 적재하여 여러 작업을 병렬적으로 처리하거나, 대기 시간 동안 다른 프로세스를 실행할 수 있습니다. 컴퓨팅 자원을 보다 효율적으로 활용하며 시스템의 전반적인 성능을 향상시키는 효과를 볼 수 있습니다.

동시성과 하드웨어

CPU의 모습에 따라 동시성은 다양한 양상을 보인다!

동시성 달성에, 하드웨어 측면에서 CPU의 코어와 스레드가 중요한 역할을 수행합니다.
먼저 현대 CPU의 구조를 살펴보도록 하겠습니다. CPU 내부에는 n개의 코어가 있고, 각 코어는 k개의 스레드를 지원할 수 있습니다.

(편의상 인덱싱은 0이 아닌 1부터 시작했습니다.)

우리는 일상생활에서 컴퓨터를 구매할 때, 코어와 스레드의 수를 통해 CPU의 성능을 판단하곤 합니다. 예를 들어 CPU의 스펙이 ‘4코어(혹은 쿼드 코어) 8스레드’라고 하면, CPU에 4개의 코어가 있고 각 코어에 2개의(= 스레드 수 / 코어의 수) 스레드가 존재함을 의미합니다. 그렇다면 코어와 스레드가 무엇인지, 코어 및 스레드의 개수가 의미하는 바는 무엇인지 알아보도록 하겠습니다.

코어(Core)

CPU가 실제로 명령을 수행하는 물리적인 처리 장치
여러 코어가 있는 경우 각 코어는 동일한 시점에 각각 하나의 작업을 처리 가능
(N개의 코어가 있는 경우, 같은 시점에 N개의 작업을 같은 시점에 실행 가능)

스레드(Thread)

물리적인 처리 단위인 코어를 논리적인 실행 단위로 나눈 것
각 코어당 한 개 이상의 스레드가 존재

코어 혹은 스레드의 개수에 따라, 여러 형태의 동시성을 실현할 수 있습니다.

동시성의 형태

동시성에는 두 가지 형태가 있습니다. 각 형태의 특징을 알아보도록 하겠습니다.

  • 병렬성
  • 병행 처리

병렬성(Parallelism)

코어의 개수는 곧 같은 시점에 실행될 수 있는 작업의 개수

‘병렬성’은 ‘한 시점에서 두 개 이상의 작업들이 함께 실행되고 있음’을 의미합니다.

병렬 컴퓨팅(Parallel computing)
다수의 연산 혹은 프로세스들이 동시에(simultaneously) 실행되는 처리 방식 (출처 : Wikipedia)

병렬성은 동시성의 하위 개념임에 유의해야 합니다.

관계 : 병렬성 → 동시성

  • 병렬성을 달성한 것은 동시성도 달성한 것
  • 동시성은 병렬성이 없이도 달성 가능
    (동시성을 달성했다고 병렬성도 달성한 것으로 볼 수는 없음)

병렬성은 하드웨어적으로 2개 이상의 코어가 각각의 스레드를 동시에 실행할 때 성립합니다. 즉, 멀티코어 CPU에서 각 코어가 독립적으로 여러 작업을 동시에 수행하고 있어야 합니다. 참고로, 2개 이상의 코어를 두고 있다면 코어 당 스레드의 수가 몇 개인지에 관계 없이 병렬성은 성립합니다. (이유는 굳이 설명하지 않겠습니다.)

예를 들어, 4코어 4스레드 사양의 CPU가 있다고 하면 아래와 같은 CPU와 실행 모습을 보일 것입니다. 각 코어가 독립적으로 한 개의 스레드를 활용해 각자의 작업을 실행하고 있기에, 병렬성을 달성한 모습입니다.

병행 처리

싱글 코어여도, 여러 작업을 빠르게 바꿔가며 실행한다면?

동시성은 용어 이름으로만 보았을 때 반드시 같은 순간에 여러 작업이 수행되어야 할 것으로 생각할 수 있습니다. 그러나 하나의 코어가 여러 작업을 짧은 시간 간격으로 번갈아 실행하여, 여러 작업이 동시에 진행되는 것처럼 보이도록 하는 것도 동시성의 한 형태입니다.

하나의 코어를 두더라도, 문맥 교환(Context Switching)을 통해 각 소프트웨어 스레드를 빠르게 전환하며 여러 작업을 실행할 수 있습니다. 같은 코어에 속해있는 스레드끼리는 자원을 공유하기 때문에, 문맥 교환 과정에서 데이터를 쉽게 공유하고 교환할 수 있습니다. 이를 통해 우리의 눈에는 여러 작업이 같은 순간에 실행되는 것처럼 보이도록 할 수 있습니다.

(예시 1) 1코어 4스레드

4개의 프로그램을 싱글 코어 CPU로 돌리는 상황을 살펴보겠습니다. 1개의 코어에 4개의 스레드가 존재한다고 가정해보겠습니다.

여러 스레드(코어 내에서 실행되는 가상의 실행 단위)가 한 코어에서 번갈아 실행되며 동시성을 달성한 모습입니다. 본 예시에서는 각 작업이 스레드에서 시분할 방식을 통해 짧은 간격으로 번갈아가며 실행되고 있습니다. 따라서 사용자에게는 여러 작업이 동시에 진행되는 것처럼 보입니다.

(예시 2) 2코어 4스레드

2개의 코어가 각각 독립적으로 작업을 수행할 수 있어, 병렬성을 달성할 뿐만 아니라, 각 코어에서 여러 스레드를 번갈아 실행하여 병행 처리의 효과도 얻을 수 있습니다. (예시 1)과 같은 작업들을 돌린다고 가정했을 때, 하나의 코어를 사용했을 때보다 같은 시간 동안 더 많은 작업량을 처리할 수 있음을 확인할 수 있습니다.

생각해보기

  • 코어 혹은 스레드의 개수가 늘어날 수록 성능 측면에서 무조건 좋은 것일까?
  • 이상적인 코어와 스레드의 수를 어떻게 결정할까?

멀티태스킹(Multitasking)

The concurrent execution of multiple tasks (also known as processes) over a certain period of time. (출처 : Wikipedia)

멀티태스킹은 CPU가 여러 작업을 빠르게 전환(concurrent execution)하며 실행하여, 마치 동시에 수행되는 것처럼 보이게 하는 기법입니다. 운영체제에서는 멀티태스킹을 관리하기 위해 두 가지 방식을 활용할 수 있습니다.

  • 선점형 멀티태스킹
  • 비선점형(협력적) 멀티태스킹

선점형 멀티태스킹(Preemptive Multitasking)

운영체제 : 작업 = 주인 : 노예

선점형 멀티태스킹에서는 운영체제가 주도적으로 각 작업의 실행을 관리하며, 작업들에 고정된 시간 슬라이스(Quantum)를 할당하여 순환적으로 실행시킵니다. 앞선 예시에서 살펴 보았던 시분할을 활용한 병행 처리 방식이 적용된 모습을 생각해볼 수 있습니다.

두 작업(T1, T2)을 선점형 멀티태스킹 방식으로 실행한 모습을 아래 그림으로 표현해볼 수 있습니다.

T1에 할당된 CPU 시간이 소진되면, 운영체제가 T1을 강제로 중단하고 T2가 CPU 자원을 활용하도록 합니다. 이 방식은 특정 작업이 CPU를 독점하지 않도록 할 수 있다는 것이 특징입니다.

비선점형/협력적 멀티태스킹(Non-Preemptive/Cooperative Multitasking)

운영체제가 실행 중인 특정 프로세스에서 다른 프로세스로의 문맥 교환을 일체 하지 않는 멀티태스킹 방식 (출처 : Wikipedia)

비선점형(협력적) 멀티태스킹은 운영체제가 각 태스크의 동작 시간을 관리하지 않고, 태스크가 자발적으로 제어권을 다른 태스크에 넘김으로써 동시성을 달성하는 방식입니다.

운영체제가 개입하지 않기 때문에, 하나의 태스크가 과도하게 CPU를 점유하면 다른 태스크가 실행될 기회를 얻지 못할 수도 있습니다. 따라서 태스크는 특정한 상황에서 적절하게 제어권을 넘겨야 합니다. 일반적으로 다음과 같은 상황에 태스크가 제어권을 넘깁니다.

  • 정해진 주기에 도달했을 때
  • 유휴 상태/블로킹 된 상태일 때

코루틴(Coroutine)과의 관계

코루틴
중단과 재개가 가능한 함수
협력적 멀티태스킹을 위한 함수의 일반화된 개념 (출처 : Wikipedia)

코루틴(Coroutine)은 특정 프로그래밍 언어에 국한된 개념이 아니라, 일반적인 프로그래밍 기법 중 하나입니다.

일반적인 함수와 코루틴을 가르는 가장 중요한 차이실행 흐름을 중단(suspend)하고, 이후 다시 이어서(resume) 실행할 수 있는 특성입니다.

일반적인 함수는 호출되면 시작 지점부터 끝까지 한 번에 실행되고, 실행이 종료되면 이전 상태를 유지하지 않습니다. 반면, 코루틴은 실행 도중에 중단할 수 있으며, 다시 실행될 때 중단된 시점의 상태를 유지한 채 이어서 실행됩니다.

  • 일반 함수 - 일단 종료되면 가지고 있던 기존의 함수 실행 맥락들은 사라짐
  • 코루틴 - 중단되더라도 실행 관련 맥락들을 그대로 보존한 채 나중에 다시 이어서 실행 가능

또한 코루틴은 제어권을 스스로 다른 코루틴으로 넘긴다는 특성이 있습니다. 운영체제의 개입 없이 코루틴 측에서 명시적으로 중단할 시점을 지정해야 한다는 의미인데, 양보(yield) 기능을 통해 스스로 중단이 가능합니다. 이는 협력적 멀티태스킹의 원리와 일맥상통합니다.

결국 코루틴을 활용하면 아래 두 가지 특성을 통해 동시성을 구현할 수 있습니다.

  • 실행 맥락을 유지하며 중단과 재개가 가능하다.
  • 제어권을 스스로 양보한다.

지금까지 살펴본 원리가 적용된 간단한 코틀린 코드를 살펴보겠습니다.

두 명의 요리사가 한 개의 테이블에서 각각 다른 요리를 준비하는 상황을 생각해 보겠습니다. 각자 요리를 수행하는 과정에서 요리사 A는 소스를 준비하고, 요리사 B는 채소를 준비합니다. 테이블을 하나만 보유하고 있는 상태에서 순차적으로 두 요리가 진행되는 것은 비효율적일 것입니다. 따라서 요리사 A가 소스를 준비하는 동안, 요리사 B가 채소를 준비할 수 있도록 테이블을 양보하면 좋을 것입니다.

한 테이블을 활용하여 두 요리사가 요리를 진행하는 모습을 그림으로 표현해 보겠습니다.

위의 그림을 코드로 나타내면 다음과 같습니다.

fun main() = runBlocking {
    launch { // 코루틴 A(요리사 A) 생성
		    // 다른 작업
        println("요리사 A: 소스 준비 시작")
        delay(1000) // 소스 준비 시간 동안 테이블 사용 중단 + 양보
        println("요리사 A: 소스 준비 완료, 요리 재개...")
        println("요리사 A : 요리 끝!")
    }

    launch { // 코루틴 B(요리사 B) 생성
		    // 다른 작업
        println("요리사 B: 채소 준비 시작")
        delay(500) // 채소 준비 시간 동안 테이블 사용 중단 + 양보
        println("요리사 B: 채소 준비 완료, 요리 재개...")
        println("요리사 B : 요리 끝!")
    }
}

제어권 양보

요리사 A가 소스를 준비하는 동안, 요리사 B가 채소를 준비할 수 있도록 제어권을 양보할 수 있습니다. 이는 코루틴에서 yield() 함수를 통해 구현할 수 있습니다. 즉, 한 코루틴에서의 yield() 실행은 ‘다른 코루틴이 실행될 수 있도록 양보함’을 의미합니다.

delay 함수

코틀린 코루틴에서는 delay() 함수가 yield() 기능을 포함하고 있습니다. 이는 코루틴이 실행 중인 작업을 설정한 시간 동안 일시 중단하고 다른 코루틴이 실행될 수 있도록 합니다. 예를 들어, 요리사 A가 소스를 준비하는 데 1초가 필요하므로 delay(1000) 함수를 호출하면, 1초 동안 작업을 중단하고 요리사 B에게 제어권을 넘깁니다.

이렇게 두 요리사가 협력적으로 번갈아 가며 요리를 수행하는 모습을 구현함으로써, 마치 동시에 두 요리가 진행되는 것처럼 보이는 효과를 달성할 수 있었습니다. 순차적으로 만들었으면 최소 1.5초가 걸렸겠지만, 동시성을 활용하였기 때문에 1초를 약간 넘는 시간동안 만에 두 요리를 마칠 수 있었습니다.


정리하기

동시성의 필요성

만약 CPU가 하나의 프로그램만을 실행할 수 있다면, I/O 작업이 발생할 때 CPU는 유휴 상태가 됩니다. 이는 컴퓨팅 자원 활용의 효율성을 떨어뜨리게 됩니다. 이를 해결하기 위해 동시성 개념이 필요합니다.

코어와 스레드

코어는 물리적인 처리 장치로, 여러 코어가 있는 경우 각각 병렬적으로 작업을 수행할 수 있습니다. (하드웨어로서) 스레드는 코어 내부의 논리적인 실행 단위로, 하나의 코어에 여러 스레드를 둘 수 있습니다.

동시성과 병렬성

동시성은 여러 프로그램을 실행할 수 있는 시스템의 특성으로, 병렬성과 병행 처리를 통해 달성할 수 있습니다.

병렬성은 여러 작업이 같은 시점에 실행되는 방식으로, 멀티 코어가 필요합니다. 병행 처리는 여러 작업이 짧은 시간 간격으로 번갈아 실행되어 동시에 진행되는 것처럼 보이는 방식으로, 싱글 코어에서도 여러 개의 스레드를 두면 달성 가능합니다. 병렬성을 달성하였다는 것은 곧 동시성을 달성한 것이지만, 그 역은 반드시 성립하지 않습니다.

멀티태스킹

동시성의 원리에 따라 멀티태스킹을 구현할 수 있는데, 이는 여러 프로그램이 짧은 시간 간격으로 번갈아 실행되도록 하는 기법입니다.

선점형 멀티태스킹은 운영체제가 각 작업에 시간 슬라이스를 할당하여 직접 각 작업의 실행을 관리합니다. 반면 비선점형(협력적) 멀티태스킹은 각 작업이 자발적으로 제어권을 양보해 가며 동시성을 달성하는데, 코루틴에서 이 원리를 활용하고 있습니다.


다음 편에 계속

다음 편에서는 코루틴의 근간이 되는 더 자세한 원리들을 프로세스스레드 개념과 함께 살펴보도록 하겠습니다.

참고 자료

https://en.wikipedia.org/wiki/Computer_multitasking
https://en.wikipedia.org/wiki/Cooperative_multitasking
https://littleosbook.github.io/#multitasking
https://m.blog.naver.com/kangyh5/223204656770
https://youtu.be/QmtYKZC0lMU?feature=shared
https://iosdevlime.tistory.com/entry/iOSCombine-CPU%EC%99%80-%EC%BD%94%EC%96%B4-%EA%B7%B8%EB%A6%AC%EA%B3%A0-%ED%94%84%EB%A1%9C%EC%84%B8%EC%8A%A4%EC%99%80-%EC%8A%A4%EB%A0%88%EB%93%9C%EC%9D%98-%EA%B0%9C%EB%85%90
https://luminousmen.com/post/concurrency-and-parallelism-are-different

profile
Software Engineer

2개의 댓글

comment-user-thumbnail
2025년 4월 11일

좋은 글 잘 읽었습니다! 설명 너무 잘해주셔서 이해하는데 어렵지 않았습니다.
"코루틴과의 관계" 파트에서 요리하는 것에 비유한 점이 특히 인상깊었습니다!

하나 피드백 드려볼 것이 있는데요! 소스와 채소를 준비하는 코루틴 요리사 A와 B를 각각 노란색, 주황색으로 나타낸걸로 이해했습니다. 그렇다면 그림에서 나타나는 두 요리사는 각각 다른 색을 띄어야 할 것 같습니다...!

  • 첫 번째 그림 : 소스 준비 요리사 A(노란색)가 테이블 위, 채소 준비 요리사 B(주황색)가 뒤에 대기
  • 두 번째 그림 : 채소 준비 요리사 B(주황)가 테이블 위, 소스 준비 요리사 A(노랑)는 뒤에서 대기(소스 준비중)
    ...

이런 그림이라면 더 이해가 잘 될 것 같습니다...!
그림의 각 장면마다, 두 요리사 모두 같은 색(노란색 또는 주황색)을 띄고 있어 조금 헷갈렸습니다.

1개의 답글