본문 바로가기
도서/코틀린 코루틴

Thread VS Coroutine

by 안스 인민군 2022. 9. 29.

나는 코틀린을 사용하다보니 코루틴을 사용하는데 이를 가지고 비동기 처리를 했다.

하지만 쓰레드와 비슷한 동작을 하지만 다르다고 한다. 그 차이점을 작성해보고자 한다.

그전에 잠깐 기본 CS에 대해 알아보도록 하자.


Process : 보조기억장치의 '프로그램'이 메모리 상으로 적재되어 실행되면 '프로세스'가 된다.
Thread : 같은 Process 내에서 실행되는 여러 작업 (흐름)의 단위

동시성 (Concurrency)

동시성 프로그래밍은 말 그대로 동시에 여러 작업을 수행하는 것이다. 하지만 눈으로 보기에 동시에 실행되는 것이지, 사실 시분할(Interleaving) 기법을 활용하여 여러 작업을 조금씩 나누어서 번갈아가며 실행하는 것이다.

위 예제를 살펴보면, Task 1 과 Task 2 를 잘개 쪼개어 번갈아가며 수행하여 사용자 입장에선 두 작업이 동시에 일어나는 것처럼 느껴지게 된다. 따라서 10분 + 10분, 총 20분이 소요된다. (Context Switching 고려 X)

병렬성 (Parallelism)

병렬성 프로그래밍은 시분할 기법없이 찐으로 여러 작업을 한 번에 동시에 수행하는 것인데, 자원(CPU 코어)의 입장에선 자기는 자기 할 일 1개를 하는 것 뿐이다. 즉, 병렬성은 '자원(CPU 코어)이 여러 개' 일 때 가능하다.

위 예시를 보자. Task 1 과 Task 2 가 병렬적으로 동시에 수행된다. 이는 '멀티코어' 시스템에서 가능한 이야기이다. 코어 각각이 Task 1, Task 2 를 맡아 작업을 수행하면, 완전히 동시에 수행되는 것이다. 따라서, 각각 태스크가 10분씩이라면, 최종적으로 10분이 소요되는 것이다. 이 경우 Context Switching 이 일어나지 않는다.

 

동시성 병렬성 이야기는 왜..?

Thread 와 Coroutine 은 모두 '동시성 프로그래밍'을 위한 기술이다. 이제부터, '동시성'을 보장하기 위해 등장한 두 기술인 Thread 와 Coroutine 의 개념과 차이점에 대하여 자세히 알아보자.


Thread vs Coroutine

개요

  • 쓰레드는 각 태스크에 해당하는 스택 메모리를 할당받는다. 그리고 여러 작업을 동시에 수행해야할 때 OS 는 어떤 쓰레드 작업을 먼저 수행할지, 어떤 쓰레드를 더 많이 수행해야 효율적인지에 대한 스케쥴링 (선점 스케쥴링, Preempting Scheduling) 을 해야 한다.
  • Coroutuine  Lightweight Thread 라고 부른다. (코틀린 기준, 공식 문서에도 이렇게 설명되어 있다) 마찬가지로 작업 하나하나를 효율적으로 분배해서 동시성을 보장하는 것을 목표로 하지만, 작업 하나하나에 Thread 를 할당하는 것이 아닌 'Object' 를 할당해주고, 이 Object 를 자유롭게 스위칭함으로써 Context Switching 비용을 대폭 줄인 것이다.

Thread

  • 작업 하나하나의 단위 : Thread
    • 각 Thread 가 독립적인 Stack 메모리 영역 가짐
  • 동시성 보장 수단 : Context Switching
    • 운영체제 커널에 의한 Context Switching 을 통해 동시성 보장
    • 블로킹 (Blocking) : Thread A 가 Thread B 의 결과가 나오기까지 기다려야 한다면, Thread A 은 블로킹되어 Thread B 의 결과가 나올 때 까지 해당 자원을 못 씀

아래 예시 사진을 살펴보자.

Thread A 에서 Task 1 을 수행하다가 Task 2 의 결과가 필요할 때, 비동기적으로 Thread B 를 호출을 하게 된다. 이 때 Thread A 는 블로킹되고, Thread B 로 Context Switching 이 일어나 Task 2 를 수행한다. Task 2 가 완료되면, 다시 Thread A 로 Context Switching 이 일어나 결과값을 Task 1 에 반환한다.

동시에 같이 수행할 Task 3, Task 4 는 각각 Thread C 와 D  할당되고, 총체적으로 봤을 때 커널 입장에서 Preempting Scheduling 을 통하여 각 태스크를 얼만큼 수행할지, 혹은 무엇을 먼저 수행할지를 결정하여 알맞게 동시성을 보장하게 되는 것이다.

Coroutine

  • 작업 하나하나의 단위 : Coroutine Object
    • 여러 작업 각각에 Object 를 할당함
    • Coroutine Object 도 엄연한 객체이기 때문에 JVM Heap 에 적재 됨 (코틀린 기준)
  • 동시성 보장 수단 : Programmer Switching (No-Context Switching)
    • 프로그래머의 코드를 통해 Switching 시점을 마음대로 정함 (OS 관여 X)
    • Suspend (Non-Blocking) : Object 1 이 Object 2 의 결과가 나오기까지 기다려야 한다면, Object 1 은 Suspend 되지만, Object 1 을 수행하던 Thread 는 그대로 유효하기 때문에 Object 2 도 Object 1 과 동일한 Thread 에서 실행될 수 있음

이해를 위해 아래 예시 사진을 살펴보자.

작업 단위는 Coroutine Object 이므로, Task 1 을 수행하다가 비동기 작업 Task 2 가 발생하더라도, Context Switching 없이 같은 Thread 에서 Task 2 를 수행할 수 있고, 맨 오른쪽 경우처럼 하나의 Thread 에서 여러 Coroutine Object 들을 같이 수행할 수도 있다. 한 쓰레드에서 다수의 Coroutine 을 수행할 수 있고, Context Switching 이 필요없는 특성에 따라 Coroutine 을 Light-weight Thread 라고 부르는 것이다.

그런데 위 경우를 다시 보자. Thread A 와 C 가 동시에 수행되는 모습이다. 이러면 결국 동시성 보장을 위해서 Context Switching 이 필요한 경우다. 따라서, Coroutine 의 'No-Context Switching' 장점을 극강으로 끌어올리기 위해, 단일 Thread 에서 여러 Coroutine Object 를 컨트롤하는 것이 좋다. (또는 권장한다)

 

💡 여기서 알 수 있는 점

Coroutine 은 Thread 의 대안이 아니라, Thread 를 더 잘게 쪼개어 사용하기 위한 개념이다.

  • 작업의 단위를 Object 로 축소하면서 하나의 Thread 가 다수의 코루틴을 다룰 수 있기 때문에, 작업 하나하나에 Thread 를 할당하며 메모리 낭비, Context Switching 비용 낭비를 할 필요가 없음
  • 같은 프로세스 내의 Heap 에 대한 Locking 걱정 또한 사라짐

한 마디로, 코루틴은 프로그래머의 목적과 의도대로 효율성을 올릴 수 있는 동시성 보장 기법이다.