📌 루틴(Routine)이란 무엇인가?
- 루틴이란 프로그램의 일부로써 특정한 일을 실행하기 위한 일련의 명령
- 우리는 프로그래밍에서 이러한 일련의 명령을 함수라 부른다.
서브루틴이란 무엇인가?
- 프로그래밍에서 함수 안에 함수가 있을 경우, 바로 안쪽의 함수를 서브루틴이라 부른다.
예를 들어 아래와 같은 코드가 있다고 하자
fun routine1() {
routine2() // routine2는 routine1의 서브루틴
}
fun routine2() {
println("Routine2")
}
- 위 코드에서 routine1을 실행하면 routine2가 속에서 수행된다.
- 우리는 이러한 routine2를 routine1의 서브루틴이라고 한다.
- 서브루틴은 루틴에 대해 순차적으로 수행된다는 특징이 있다.
- 만약 루틴이 수행 되지 않을 경우 서브루틴 또한 수행되지 않는 것
“함수 안에 함수가 있을 경우 바로 안쪽의 함수를 서브루틴이라고 부른다.”
📌 코루틴이란?
- 함께(Co) 수행되는 함수(Routine)
- 위 그림에서 Coroutine1 안에서 Coroutine2가 수행되는 것이 아니다. 각각은 서로 다른 함수(Routine)이며, 함께 수행되고 있는 것이다.
- 이들이 하나의 스레드를 점유하고 있을 때 한 Routine이 다른 Routine에게 “스레드 권한을 양보함”으로써 함께 수행되는 것이다.
- 이는 서브루틴과 다른데, 서브루틴은 순차적으로 수행되어야 하지만 코루틴은 함께 수행되며 서로 무제한 양보를 할 수 있다.
- 이렇게 하면 스레드의 자원을 최대한 활용할 수 있어 효율적이다.
📌 block(막다, 차단하다) vs suspend(유예하다, 연기하다)
block
단일 스레드에서 네트워크 요청(FUNCTION A)과 같은 작업을 수행하다 보면 응답을 얻는 데까지 긴 시간이 필요할 수 있다. 이 동안 해당 스레드는 블록 상태(blocked)가 되고 다음 작업(FUNCTION B)은 네트워크 작업이 끝날 때까지 기다리게 된다. 그래서 일반적으로 다중 스레드를 사용하여 비동기적으로 작업을 수행한다.
suspend
코루틴의 suspend 함수는 네트워크 요청(FUNCTION A)을 수행하고 스레드가 블록 되는 대신 하던 작업을 정지(suspended) 시킨다. 그리고 다른 작업이 해당 스레드를 사용할 수 있도록 한다. 하나의 스레드에서 여러 작업을 수행할 수 있으니 스레드를 만들 필요가 없다. 이러한 이유로 코루틴을 경량화된 스레드(Light-weighted Thread)라고 표현되는 이유이다.
그렇다면 코루틴과 스레드의 메모리 구조적 차이를 알아보자
📌 코루틴과 스레드의 메모리 구조 차이
모바일에서 프로그램을 실행을 시키면 메모리에 로드가 된다. 이때 메모리 안에 로드된 인스턴스는 프로세스라고 한다.
이렇게 실행된 프로세스는 그 안에서 여러 개의 독립된 실행의 흐름을 가지게 된다. 이때 각각의 흐름을 스레드라고 한다.
프로세스는 자기가 사용하기 위해서 메모리 영역을 할당받는데 이를 힙(Heap) 메모리라고 하고 프로세스 안의 각각의 스레드에도 메모리가 할당되는데 이를 스택(Stack) 메모리라고 한다.
코루틴과 스레드의 가장 큰 차이는 스레드는 Stack이라는 독립된 메모리 영역을 할당받게 되는데 코루틴은 Stack을 할당받지 않고 프로세스에 할당된 Heap 메모리 영역을 공유해서 사용한다.
따라서 스레드의 경우 한 스레드에서 다른 스레드로 작업이 넘어갈 때 Context Switching이 발생하지만 코루틴은 Heap 메모리를 공유하기 때문에 Context Switching이 필요하지 않다. 그로인해 사용하는 메모리도 적어지고 Context Switching에 필요한 오버헤드도 줄어들게 된다.
📌 코루틴 구조
Coroutine Scope
- Coroutine Scope란 코루틴이 동작하는 범위를 규정한다.
- Scope가 결정이 되면 특정한 Dispatcher를 지정을 해서 동작이 실행될 영역을 정한다.
CoroutinScope
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
GlobalScope
- 앱에서 최상위 레벨에서 사용되는 싱글톤이기 때문에 일반적으로 사용하지 않는다.
Coroutine Context
- 코루틴은 항상 Kotlin 표준 라이브러리에 정의된 CoroutineContext 유형의 값으로 표시되는 컨텍스트에서 실행된다.
- 이 중 Dispatchers는 코루틴이 실행되는 스레드를 지정하게 된다.
- 코루틴 Dispatcher의 종류는 다음과 같다.
- Dispatcher.Main : Android 메인 스레드에서 코루틴을 실행하는 Dispatcher. UI와 상호작용하는 작업이라는 꼭 이 Dispatcher에서 작업을 수행해야 한다.
- Dispatcher.IO : 디스크 또는 네트워크 I/O작업에 최적화 되어있는 Dispatcher이다. 일반적으로 IO 작업은 CPU에 영향을 미치지 않으므로, CPU 코어 수보다 훨씬 많은 스레드를 가지는 스레드 풀에서 수행한다(최대 64개).
- Dispatcher.Default : CPU를 많이 사용하는 작업을 기본 스레드 외부에서 실행하도록 최적화 되어있는 Dispatcher이다. CPU 코어 수에 비례하는 스레드 풀에서 수행한다. CPU 집약적인 작업, 즉 CPU 바운드 작업에 적합하다.
- newSingleThreadContext(name: String) : 코루틴을 실행할 수 있는 새로운 스레드를 생성하여 작업한다.
- Job & Defferred는 코루틴이라는 추상적인 흐름을 Job과 Deffered라는 객체로 만드는 것이다.
- 객체로 만듬으로써 취소나 예외처리를 할 수 있게되고 이는 코루틴 흐름제어를 용이하게 한다.
- Job & Deferred(결과값을 가지는 Job)
val job = scope.launch {
// New Coroutine
}
위에서 말했다 싶이 코루틴을 객체로 만듬으로써 취소나 예외처리를 할 수 있다고 했다.
- Job은 위 표처럼 여러 상태(State)를 가질 수 있다.
- 이러한 Job은 cancel, start, join과 같은 메소드가 존재한다.
Coroutine Builder
코루틴이 Context까지 정해졌다면 이제 Builder를 통해 만들어주면 된다.
코루틴 빌더
- launch
- Job 객체 반환
- async
- Deferred 객체 반환
- runBlocking
- UI 스레드 즉 메인 스레드를 Block 시키고 작업을 수행
- 테스트 용도로는 사용하긴 하지만 일반적으로 사용 X
- withContext
- Dispatcher switch
- 예를 들어 Main에서 작업하다가 중간에 네트워크 IO가 필요할 수 있다.
- 이 때 Dispatcher 안에 다른 Dispatcher를 만들어서 작업하는 것이 아니라
- withContext를 통해 딱 필요한 작업만을 Dispatcher IO에서 수행한 뒤 Dispatcher Main으로 돌아가게 할 수 있다.
- 이렇게 withContext를 통해 Dispatcher를 스위치를 하면 안드로이드 OS에서 스위칭을 관리하기 때문에 오버헤드가 적다.
코루틴 지연
- delay
- 스레드의 sleep 함수와 비슷하지만 스레드 sleep은 스레드 그 자체를 멈추는데 반해
- delay는 코루틴이 멈추는 것이 아닌 코루틴이 숫자를 세며 대기상태에 들어간다.
- join
- launch로 실행한 코루틴은 join으로 Job의 수행이 끝날 때 까지 대기할 수 있다.
- await
- async로 실행한 코루틴은 await으로 Deffered의 수행이 끝날 때 까지 대기한 후 값을 반환한다.
코루틴 취소
- cancel
- cancelAndJoin
- withTimeout
- withTimeoutOrNull
예외 처리
- CoroutineExceptionHandler(CEH)를 이용하여 코루틴 내부의 기본 catch block으로 사용할 수 있다.
- lauch : exception 발생 시 바로 예외가 발생
- async : 중간에 exception이 발생해도 await를 만나야 발생
- job.cancel()을 제외한 다른 exception이 발생하면 부모의 코루틴까지 모두 취소시킴. 이는 structured cocurrency를 유지하기 위함으로 CEH를 설정해도 막을 수 없다.
- structured cocurrency란 구조화된 동시성이라는 뜻으로 부모의 작업이 끝나기 전에 자식의 작업이 끝남을 보장하는 것이다.
- 여러개의 exception이 발생하면 가장 먼저 발생한 exception이 Handler로 전달되며 나머지는 무시된다.
'Android' 카테고리의 다른 글
안드로이드 [Kotlin] - 아키텍처 패턴 with MVC, MVP, MVVM (feat 코드 예제) (0) | 2022.06.26 |
---|---|
안드로이드 [Kotlin] - 코루틴(Coroutine) 2 - 코루틴 실습(생성과 취소) (0) | 2022.05.25 |
안드로이드 - Fragment Lifecycle (프래그먼트 생명주기) (0) | 2022.05.18 |
안드로이드 - Activity Lifecycle (액티비티 생명주기) (0) | 2022.05.05 |
안드로이드 [Kotlin] - 코드스쿼드 미션 중요 내용 및 피드백 정리 (0) | 2022.05.04 |