코루틴
코루틴이 나오기 이전에 앱이나 웹에서 비동기 처리를 위해 rx programming을 많이 사용해왔다. 하지만 구글이 안드로이드 공식 언어를 코틀린으로 변경한 이후 코루틴에 대한 중요도가 높아졌고,
현재는 안드로이드에서 가장 많이 쓰이는 비동기 처리 기술이라고 개인적으로 생각한다.
코루틴은 스레드 안에서 실행되는 일시 중단 가능한 작업의 단위로 하나의 스레드에 여러 코루틴이 존재할 수 있다.
코루틴은 재개될 때마다 다른 스레드에서 실행될 수 있고, 또는 특정 스레드에서만 국한되어 사용될 수 있다.
코루틴은 '경량 쓰레드'라고도 불린다.
작업 하나하나를 효율적으로 분배해 쓰레드를 할당하는 것이 아닌 'Object'를 할당하고, 이 Object를 자유롭게 스위칭하며 기존에 스레드 변경할 때 발생했던 Context Switching이 비용을 줄인다!
또한 코루틴은 Object이기 때문에 Heap 영역에 적재된다.
코루틴의 특징 중 중요한 부분이 바로 이 두가지 이다.
협력형 멀티 태스킹
동시성 프로그래밍 지원
협력형 멀티태스킹
코루틴(Co + Routine)에서 Co는 "협력" 이라는 의미를 지니고 있고, Routine은 하나의 함수라고 생각하면 된다.
즉, "협력하는 함수"다.
Routine에 대해 간단히 알아보면 main routine과 sub routine으로 나뉜다.
fun main(){
...
val beerCount = drinkBeer(value)
...
}
fun drinkBeer(value : Int){
val one = 1
val beerCount = value + one
return beerCount
}
위와 같은 코틀린 코드에서 main 함수는 메인이 되는 함수
여기서 drinkBeer라는 서브 함수를 호출한다.
메인 함수는 -> 메인 루틴, 서브 함수는 -> 서브 루틴이 된다.
이 서브 루틴의 경우 루틴에 진입하는 곳과 빠져나오는 곳이 명확하게 보인다.
메인 루틴이 서브 루틴을 호출 했을 때, 서브루틴의 맨 처음에 진입하여 return을 호출하거나 닫는 괄호를 만났을 때 이 서브 루틴을 빠져나오게 된다.
위의 코드를 예시로 들자면, drinkBeer 함수에 진입하고 return을 통해 빠져나오게 된다.
But! 코루틴은 다르다.
일단 코루틴은 routine이니 하나의 함수로 여기자.
위의 서브 루틴과 다르게 코루틴은 진입할 수 있는 곳도 여러 개이고, 함수를 빠져나갈 수 있는 곳도 여러개다.
즉, 중간에 나갈 수 있고 다시 나갔던 그 지점으로 들어올 수도 있다.
이때 나갈 수 있도록 하는 키워드가 suspend이다.
이 suspend를 붙이는 것은 코틀린 컴파일러에게 이 함수가 코루틴 안에서 실행되어야 한다고 알려주는 역할을 한다.
val scope = CoroutineScope(Dispatchers.Main)
fun homeWork(){
scope.launch{
writeCode()
writeReadMe()
gitCommit()
gitPush()
}
}
suspend fun writeCode(){
delay(2000)
}
suspend fun writeReadMe(){
delay(3000)
}
suspend fun gitCommit(){
delay(5000)
}
suspend fun gitPush(){
delay(1500)
}
homeWork라는 함수가 있다. 쓰레드의 main 함수가 이 homeWork라는 함수를 호출하면 우리가 만든 코루틴 스코프가 launch라는 코루틴 빌더를 만나 코루틴을 만들어준다.
위에서 말했다시피 이 homeWork()는 언제든지 진입할 수 있고 탈출할 수 있게 된다.
코루틴이 실행되고, suspend 키워드의 함수를 만나게 되면 -> writeCode
더 이상 그 아래의 코드를 실행하지 않고 homeWork() 라는 코루틴 함수를 탈출한다.
하지만, 탈출했더라도 메인 쓰레드는 가만히 있지 않고, 다른 코드들을 실행하거나 UI 작업을 진행할 수도 있다. 그렇지만 어디서 writeCode는 계속 실행되고 있다. 이때 다른 쓰레드에서 실행될 수도 있고, 동시성 프로그래밍으로 작동될 수도 있다.
메인쓰레드가 다른 코드를 실행하고 있다가도, writeCode()가 다 실행되었을 때 다시 탈출했던 homeWork() 코루틴으로 돌아오고, 그 아래인 writeReadMe()부터 다시 재개(resume) 된다.
동시성 프로그래밍
동시성 프로그래밍 -> 여러 가지 작업이 동시에 처리되는 것
병렬 프로그래밍 -> 정말 동시에 같이 처리하는 것
동시성 프로그래밍과 병렬 프로그래밍은 위의 설명에서 보기엔 같은 개념인 것 같지만 다르다.
먼저 동시성 프로그래밍의 경우
노트북 한개에 모니터 두개를 연결한 모습이다.
이때 왼쪽 모니터에는 안드로이드 스튜디오가, 오른쪽 모니터에는 ReadMe가 켜져있다.
안드로이드 스튜디오에서 코드를 작성하다가, 재빠르게 오른쪽 모니터로 눈을 옮겨 ReadMe를 작성한다. 그리고 이러한 행동을 엄청 빠르게 반복한다.
이게 동시성 프로그래밍 이다.
반면, 병렬성 프로그래밍은
노트북 두개를 왼손으로 코드 작성, 오른 손으로 ReadMe를 작성하는 것이다.
A
val scope = CoroutineScope(Dispatchers.Main)
fun homeWork(){
scope.launch{
writeCode()
writeReadMe()
gitCommit()
gitPush()
}
}
suspend fun writeCode(){
delay(2000)
}
suspend fun wrtieReadMe(){
delay(3000)
}
B
val scope = CoroutineScope(Dispatchers.Main)
fun homeWork(){
scope.launch{
writeCode()
writeReadMe()
gitCommit()
gitPush()
}
}
suspend fun writeCode(){
delay(2000)
}
suspend fun wrtieReadMe(){
delay(3000)
}
코루틴도 루틴이기 때문에 하나의 쓰레드에 여러개가 존재가 가능하다.
위의 코드에서 메인 쓰레드 안에 코루틴이 두개가 있다. 하나는 왼쪽 모니터, 하나는 오른쪽 모니터라고 생각하면 편하다.
왼쪽 코루틴에서 suspend 키워드의 함수를 만나 빠져 나와 다른 코루틴안의 suspend 함수를 만나게 되면, 이때 쓰레드 하나에서 동시성 프로그래밍이 가능해진다.
코루틴 사용하기
코루틴에서 주로 사용하는 키워드들은 다음과 같다.
- CoroutineScope
- CoroutineContext
- Dispatcher
- launch & async
CoroutineScope
public interface CoroutineScope {
public val coroutineContext: CoroutineContext
}
CoroutineScope는 기본적으로 아래에 나올 CoroutineContext 하나만 멤버 속성으로 정의하고 있는 인터페이스이다.
이 CoroutineScope의 확장 함수가 모든 코루틴 빌더(launch, async), 스코프빌더(coroutineScope, withContext)이다.
단, GlobalScope도 쓸 수 있지만 코루틴의 생명주기를 제어하기 원한다면 권장하지 않는다.
val scope = CoroutineScope(Dispatchers.Main)
fun homeWork(){
scope.launch{
}
코루틴의 실행을 멈추기 위해서 cancel() 메소드를 활용한다.
scope.cancel()
Coroutine Builder
1. launch -> Job 반환
- 현재 쓰레드를 블로킹하지 않고 새로운 코루틴을 시작하고, 이에 대한 참조를 job 객체로 반환한다.
- job이 취소되면 코루틴이 취소된다.
- 기본은 Dispatchers.Default가 지정되지만 추가적인 Context를 지정할 수 있다.
2. runBlocking -> T 반환
- 새 코루틴을 실행하고 완료될 때까지 현재 사용하고 있는 스레드의 인터럽트를 차단한다.
- 코루틴에서 사용하지 않는 것을 권한다. -> 동시성 보장 X
3. async -> Deffered<T> 반환
- 코루틴을 만들고 Defferedd의 구현으로 해당 결과를 반환한다.
- 실행 중인 코루틴은 Deffered의 결과가 취소되면 취소된다.
4. withContext() -> T반환
- async와의 차이
- async는 반환하는 Deffered<T> 객체로 결과를 원하는 시점에 await() 함수를 통해 받는다.
- withContext()는 Deffered<T> 객체로 반환하지 않고, 결과(T)를 그자리에서 반납한다.
CoroutineContext
CoroutineContext의 주요 요소로 Job과 Dispatcher가 있다.
Coroutine Context는 Job, CoroutineName, CoroutineDispatcher, CoroutineId, 등등과 같은 Element 인스턴스를 순서가 있는 set으로 관리한다.
그리고 모든 element에는 이를 식별하기 위한 고유한 key를 가지고 있고, 이 key가 CoroutineContext에 등록된다.
Dispatcher
어떤 스레드를 이용해서 어떻게 동작할 것인지를 정의한다.
IO -> 네트워크나 디스크 작업을 할 때 사용한다.
Default -> CPU 사용량이 많은 작업, 메인 스레드에서 작업하기에 너무 긴 작업
Main -> UI 작업이나, 쓰레드를 블락하지 않고 빨리 실행되는 작업에 사용한다.
참고
https://seunghyun.in/android/7/
https://wooooooak.github.io/kotlin/2019/08/25/코틀린-코루틴-개념-익히기/
'Language > kotlin' 카테고리의 다른 글
[Kotlin] lateinit & lazy (0) | 2024.08.08 |
---|---|
[Kotlin] 오버로딩 & 오버라이딩 (0) | 2024.08.08 |
[Kotlin] 스코프함수(Scope function) (1) | 2024.07.05 |
[Kotlin] 추상 클래스 & 인터페이스 (0) | 2024.06.10 |
[kotlin] Data class 란? (0) | 2024.05.29 |