Coroutine과 Thread 비교

HEETAE HEO·2022년 6월 25일
0
post-thumbnail

둘을 비교하기 전에 알아야하는 선수지식들에 대해서 다시 간단하게 설명하고 본격적인 비교를 시작해보겠습니다.

Process & Thread

Process : Program이 메모리에 적재되어 실행되는 인스턴스
Thread : Process 내 실행되는 여러 흐름의 단위

먼저 Thread는 Process 보다 작은 단위의 실행 인스턴스로만 알고 있는데, 메모리 영역도 조금 다릅니다.

Process는 독립된 메모리 영역(Heap)을 할당받고 각 Thread도 독립된 영역(Stack)을 할당받습니다. Thread는 본질적으로 Process 내에 속해있기 때문에 Heap 메모리 영역은 해당 Process에 속한 Thread 들은 공유할 수 있습니다. 프로그램에 대한 Process가 생성되면 Heap 영역과 하나의 Thread와 하나의 Stack 영역을 갖게되고 Thread가 추가될때마다 그 수만큼의 Stack이 추가됩니다. Thread가 100개라면 전체 메모리에 100개의 Stack이 생성되는 것입니다.

동시성 vs 병렬성

동시성(Concurrency)

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

Task1과 Task2를 잘게 쪼개어 번갈아가며 수행하여 사용자 입장에서는 두 작업이 동시에 일어나는 것처럼 느껴지게 됩니다. 따라서 10 + 10 = 총 20분이 걸리게 됩니다.

병렬성(Parallelism)

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

동시성과는 다르게 멀티코어 환경에서 코어가 나누어 작업을 수행하게 되므로 작업을 종료하는데 까지 10분이 걸리게 됩니다.

Coroutine & Thread

개요

  • 스레드는 각 Task에 해당하는 스택 메모리를 할당 받는다. 그리고 여러 작업을 동시에 수행해야할 때 OS는 어떤 스레드 작업을 먼저 수행할지 어떤 스레드를 더 많이 수행해야 효율적인지에 대한 스케쥴링을 해야합니다.
  • Coroutine은 Lightweight Thread라고 부릅니다. 마찬가지로 작업 하나하나를 효율적으로 분배해서 동시성을 보장하는 것을 목표로 하지만 작업 하나하나에 Thread를 할당하는 것이 아닌 object를 할당해주고 이 Object를 자유롭게 스위칭을 사용합니다. (Context Switching를 사용하지 않기에 비용이 대폭 줄어든다.)

Thread

  • Task 단위 = Thread
    -> 다수의 작업 각각에 Thread를 할당합니다.
    -> 각 Thread는 위에 설명했듯이 자체 Stack 메모리 영역을 가지며 JVM Stack 영역을 차지합니다.

  • Context Switching
    -> OS Kernel에 의한 Context Switching을 통해 Concurrency를 보장합니다.
    -> Blocking : 작업 1이 작업 2의 결과가 나오기까지 기다려야한다면 작업 1 thread는 Blocking 되어 그 시간동안 자원을 활용하지 못합니다.

위 그림에서 작업들은 모두 Thread 단위인것을 알 수 있습니다. Thread A 에서 작업 1을 수행중에 작업 2가 필요할때 이를 비동기로 호출하게 됩니다. 작업 1은 진행중이던 작업을 멈추고(Blocked) 작업 2는 Thread B에서 수행되며 이때 CPU 가 연산을 위해 바라보는 메모리 영역을 Thread A 에서 Thread B로 전환하는 Context Switching 이 일어납니다.

작업 2가 완료되었을때 해당 결과값을 작업 1에 반환하게 되고, 동시에 수행할 작업 3과 작업 4는 각각 Thread C와 Thread D에 할당됩니다. 싱글 코어 CPU는 동시 연산이 불가능하므로 이때에도 OS Kernel 의 Preemptive Scheduling에 의해 각 작업 1, 3, 4 각각을 얼만큼 수행하고 멈추고 다음 작업을 수행할지 결정하여 그에 맞게 세 작업을 돌아가며 실행함으로써 Concurrency를 보장합니다.

Coroutine

  • Task 단위 = Object(Coroutine)
    -> 다수의 작업 각각에 Object를 할당합니다.
  • Programmer Switching = No Context Switching
    -> 프로그래머의 코딩을 통해 Switching 시점을 마음대로 정해 Concurrency를 보장합니다.
    -> Suspend(Non-Blocking) : 작업 1이 작업 2의 결과가 나오기까지 기다려야한다면 작업1은 Suspend 되지만 작업 1을 수행하던 Thread는 그대로 유효하기 때문에 작업 2도 작업 1과 동일한 Thread에서 실행될 수 있다.

작업의 단위는 Coroutine Object 이므로 작업 1 수행중에 비동기 작업 2가 발생하더라도 작업 1을 수행하던 같은 Thread 에서 작업 2를 수행할 수 있으며, 하나의 Thread 에서 다수의 Coroutine Object 들을 수행할 수도 있습니다. 위 그림에 따라 작업 1과 작업 2의 전환에 있어 단일 Thread A 위에서 Coroutine Object 객체들만 교체함으로써 이뤄지기 때문에 OS 레벨의 Context Switching 은 필요없습니다. 한 Thread 에 다수의 Coroutine 을 수행할 수 있음과 Context Switching 이 필요없기 떄문에 Coroutine 을 Lightweight Thread 로도 부릅니다.

다만 위 그림의 Thread A와 Thread C의 예처럼 다수의 스레드가 동시에 수행된다면 Concurrency 보장을 위해 두 Threads 간 Context Switching 은 수행되어야합니다. 따라서 Coroutine 을 사용할때에는 No Context Switching 이라는 장점을 최대한 활용하기 위해 다수의 Thread 를 사용하는 것보다 단일 Thread 에서 여러 Coroutine Object 들을 실행하는 것이 좋습니다.

정리

Coroutine 으로 ‘작업’의 단위를 Thread가 아닌 Object로 축소하면서 작업의 전환 및 다수 작업 수행에 굳이 다수의 Thread를 필요로 하지 않게 됩니다.

Coroutine은 Thread의 대안이 아니라 기존의 Thread를 더 잘게 쪼개어 사용하기위한 개념입니다. 하나의 Thread가 다수의 코루틴을 수행할 수 있기 때문에 더 이상 작업의 수만큼 Thread를 양산하며 메모리를 소비할 필요가 없습니다.

각 스레드마다 갖는 Stack 메모리 영역을 갖지 않기때문에, 스레드 사용시 스레드 개수만큼 Stack 메모리에 따른 메모리 사용공간이 증가하지 않아도 됩니다. 같은 프로세스내에 ‘공유 데이터 구조’(Heap)에 대한 locking 걱정도 없습니다.

profile
Android 개발 잘하고 싶어요!!!

0개의 댓글