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

Coroutine, Thread 와의 차이와 그 특징

by 안스 인민군 2023. 7. 17.
 

Coroutine, Thread 와의 차이와 그 특징

처음 Kotlin 를 사용하던 중에 비동기 처리를 위해 Coroutine 개념을 마주했었습니다. 동기란 요청을 보낸 후 요청에 대한 반환값을 얻기 이전까지 대기하는걸 의미하고, 비동기는 그 대기시간동안

aaronryu.github.io

Process & Thread

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

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

Process 는 독립된 메모리 영역(Heap)을 할당받고 각 Thread도 독립된 메모리 영역(Stack)을 할당받는다. Thread 는 본질적으로 Process 내에 속해있기 때문에 Head 메모리 영역은 해당 Process 에 속한 모든 Thread 들이 공유할 수 있다.

Program 에 대한 Process 가 생성되면 Heap 영역과 하나의 Thread 와 하나의 Stack 영역을 갖게되고, Thread 가 추가될때마다 그 수만큼의 Stack 이 추가된다. Thread 가 100 개라면 전체 메모리에 100 개의 Stask 이 생성되는 것이다.

Concurrency & Parallelism

Concurrency 동시성

Interleaving, 시분할: 다수의 Task 가 있는데, 각 Task 들을 평등하게 조금씩 나누어 실행하는것

총 실행시간은 Context Switching 에 대한 비용을 제외하곤 각 Task 수행시간을 합친것과 동일하다.
예를 들어 3 개의 Task 각각이 10분씩 걸린다고 했을때, 총 30분이 소요되는것이다.

Parallelism 병렬성

Parallelizing, 병렬수행: 다수의 Task 가 있는데, 각 Task 들이 한번에 수행되는 것

Task 수 만큼 자원이 필요하며, Context Switching 은 필요없다.
총 실행시간은 다수의 Tasks 중 가장 소요시간이 긴 Task 만큼 걸린다.
예를 들어 3 개의 Task 각각이 10, 11, 12분씩 걸린다면, 총 12분이 소요되는 것 이다.

Thread & Coroutine

Thread, Coroutine 모두 Concurrency 동시성 (Interleaving) 를 보장하기 위한 기술이다. 여러개의 작업을 동시에 수행할 때 Thread 는 각 작업에 해당하는 메모리 영역을 할당하는데, 여러 작업을 동시에 수행해야하기 때문에 OS 레벨에서 각 작업들을 얼만큼씩 분배하여 수행해야지 효율적일지 Preempting Scheduling 을 필요로 하다. A 작업 조금 B 작업 조금을 통해 최종적으로 A 작업과 B 작업 모두를 이뤄내는 것 이다. Coroutine 은 Lightweight Thread 라고 불린다. 이 또한 작업을 효율적으로 분배하여 조금씩 수행하여 완수하는 Concurrency 를 목표로하지만 각 작업에 대해 Thread 를 할당하는 것이 아니라 작은 Object 만을 할당해주고 이 Object 들을 자유자재로 스위칭함으로써 Switching 비용을 최대한 줄인다.

Thread

  • Task 단위 = Thread
    • 다수의 작업 각각에 Thread 를 할당한다.
      각 Thread 는 위에 설명했듯 자체 Stack 메모리 영역을 가지며 JVM Stack 영역을 차지한다.
  • Context Switching
    • OS Kernel 에 의한 Context Switching 을 통해 Concurrency 를 보장한다.
    • Blocking: 작업 1(Thread) 이 작업 2(Thread) 의 결과가 나오기까지 기다려야한다면
      작업 1 Thread 는 Blocking 되어 그 시간동안 해당 자원을 사용하지 못한다.

* 쉬운 설명을 위해 CPU 는 Single Core 로 가정한다.

위 그림에서 작업들은 모두 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 의 Preempting Scheduling 에 의해 각 작업 1, 3, 4 각각을 얼만큼 수행하고 멈추고 다음 작업을 수행할지 결정하여 그에 맞게 세 작업을 돌아가며 실행함으로써 Concurrency 를 보장한다.

 

Callback지옥으로부터 Coroutine까지의 긴 여정

지금까지 비동기 작업의 처리를 위해서 해왔던 과정들을 되짚어봅니다. 주어진 요구사항을 Callback, High Order Function, RxJava, Coroutine으로 구현했을때의 사용방식에 대해 간단한 코드로 비교합니다.

medium.com