나는 코틀린을 사용하다보니 코루틴을 사용하는데 이를 가지고 비동기 처리를 했다.
하지만 쓰레드와 비슷한 동작을 하지만 다르다고 한다. 그 차이점을 작성해보고자 한다.
그전에 잠깐 기본 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 걱정 또한 사라짐
한 마디로, 코루틴은 프로그래머의 목적과 의도대로 효율성을 올릴 수 있는 동시성 보장 기법이다.
'도서 > 코틀린 코루틴' 카테고리의 다른 글
1장 코틀린 코루틴 이해하기 - 코틀린 코루틴을 배워야 하는 이유 (0) | 2024.01.08 |
---|---|
Retrofit 과 Coroutine 간의 Viewmodel에선 Dispacher.IO로 전환해야 할까? (1) | 2023.11.30 |
[DroidKnights 2023] 김준비 - Coroutine Deep Dive - Android 실전편 (0) | 2023.11.14 |
Coroutine, Thread 와의 차이와 그 특징 (0) | 2023.07.17 |
Coroutine 완벽 정리 (0) | 2023.02.14 |