지나공 : 지식을 나누는 공간
코루틴(coroutine)에 대하여. 1편. 코루틴을 왜 쓸까? 본문
코루틴(Coroutine)?
실행의 지연과 재개를 허용함으로써, 비선점적 멀티태스킹을 위한 서브 루틴을 일반화한 컴퓨터 프로그램 구성요소.
이제 진하게 칠한 단어를 하나씩 공부해보자!
1. 루틴과 서브루틴?
프로그램은 보통 크고 작은 여러가지의 루틴을 조합시켜서 성립되는데 여기서 메인 루틴과 서브루틴으로 나뉜다.
- 메인 루틴 : 프로그램 전체의 개괄적인 동작 절차를 표시하도록 만들어진 루틴.
- 서브 루틴 : 반복적인 특정 기능을 모아서 별도로 묶인 루틴.
서브 루틴은 별도의 메모리에 해당 기능을 모아 놓고 서브루틴이 호출될 때마다 저장된 메모리에 이동한 뒤 다시 return을 통해 원래 호출자의 위치로 돌아온다. 서브루틴은 함수와 비슷하다.
코루틴도 루틴의 일종인데, 3가지 차이점이 존재한다.
- 메인 - 서브 개념이 없고, 모든 루틴들이 서로를 호출할 수 있다.
- 진입점과 탈출점이 여러 개여서 꼭 return을 만나지 않더라도 언제든지 서브루틴 실행 도중에 나갈 수 있고, 다시 나갔던 지점으로 돌아올 수도 있다. (서브루틴은 호출되었을 때 진입하고 return을 만나야 탈출하니까 코루틴이랑 약간 다르다.)
- 서브루틴은 진입점과 탈출점이 단 하나밖에 없어서 메인 루틴에 종속적이지만 코루틴은 진입지점이 여러 개여서 메인 루틴에 종속적이지 않고 대등하게 데이터를 주고 받을 수 있다.
2. 비선점적 멀티태스킹?
비선점형 멀티태스킹과 선점형 멀티태스킹이 있다. 운영체제 시간에 많이 들어봤다.ㅎㅎ
- 비선점형 : task가 스케줄러로부터 cpu 사용권을 할당받았을 때 스케줄러가 강제로 cpu사용권을 뺏을 수 없다.
- 선점형 : task가 cpu를 사용하고 있더라도 이걸 뺏어서 중단시킬 수 있다.
코루틴은 비선점형 멀티태스킹이고, 쓰레드는 선점형 멀티태스킹이다.
즉, 코루틴은 병행성(=동시성, concurrency)는 제공하고 병렬성(parallelism)을 제공하지 않는다.
* 병행성(=동시성)과 병렬성?
병행성(=동시성)은 물리적으로 병렬실행되는 건 아니지만 마치 동시에 실행되는 것처럼 보이는 것이다.
논리적인 개념이고, 실제로는 time-sharing(시간분할)으로 cpu를 나눠 사용한다. 한 손으로 두 개의 종이에 번갈아가면서 그림을 그려서 결론적으로 두 개의 그림을 완성하는 것.
병렬성은 실제로 동시에 작업이 처리되는 것이다. 물리적인 개념이다. 두 손으로 각각 종이에 그림을 동시에 그려서 결론적으로 두 개의 그림을 완성하는 것.
그래서 코루틴이 가지는 장점이 뭔데? 위에서 말한 것을 다시 정리해보자!
1. 협력형 멀티태스킹
2. 동시성 프로그래밍 지원(마치 경량 스레드 같다.)
3. 쉬운 비동기 처리
1. 협력형 멀티태스킹이 위에서 진한 글씨로 해 놓은 코루틴의 두 번째 차이점이다.
일반함수(drawAnimal())을 호출하면 이 함수는 이제 코루틴 함수가 되고 drawAnimal()을 언제든지 진입하거나 탈출할 수 있게 된다. 그렇게 이 drawAnimal()이라는 코루틴 함수를 실행하다가 이 내부에서 suspend가 붙은 함수를 만나면 더이상 아래 코드를 실행하지 않고 코루틴 함수를 탈출한다. suspend가 붙은 함수를 실행하는 동안 main 스레드는 drawAnimal()이라는 코루틴 함수를 나와서 다른 코드를 실행한다. 다른 코드들을 실행하다가도 suspend 함수가 끝났으면 다시 코루틴 함수에 진입해서 남은 코드를 실행한다.
2. 동시성 프로그래밍 지원은 함수를 중간에 빠져나왔다가 다른 함수에 진입하고 다시 돌아와 멈췄던 부분부터 다시 시작하는 특징을 말한다.
코루틴은 개념적으로 스레드와 비슷해서 경량 스레드라고도 부르는데, 스레드의 동시성 프로그래밍과 코루틴의 동시성 프로그래밍을 보자.
한편 스레드는 앞서 말했듯이 스레드 간 교체에 많은 비용이 든다. 최근의 컴퓨터에는 하나의 cpu에 여러 코어가 있어서 여러 개를 실행하는 것처럼 보이는 게 아니라 실제로 여러 cpu로 여러 스레드를 돌리기 때문에 병렬성이 이루어진다.
코루틴이 경량스레드라고 불리는 이유는 목적이 스레드와 같지만 성능이 더 좋고 가볍기 때문이다. 스레드가 아니다!
하나의 스레드에 코루틴이 여러 개 존재할 수 있는데, 실행 중이던 하나의 코루틴이 suspend(중단)되면, 현재 스레드에서 resume(재개)할 다른 코루틴을 찾는다. 근데 이제 이 다른 코루틴을 같은 스레드 내에서 찾는 것이다. 그니까... 스레드를 바꾸는 게 아니라서 switch 비용으로 인한 오버헤드가 없다.!
그리고 스레드는 cpu 상태에 따라 알아서 처리되는데 코루틴은 개발자가 직접 처리할 수 있다.
3. 쉬운 비동기 처리는 코틀린을 통해 비동기 처리 코드를 쉽고 가독성 있게 작성할 수 있는 특징이다. 이건 ... 코드 써봐야 알 것 같다.!
코틀린의 코루틴
Kotlin1.3에 코루틴이 추가되었다. 몇 가지 요소에 대해 알아봤다.
Coroutine Scope
이건 새로운 코루틴을 생성함과 동시에, 그 안에서 실행되어야 할 job들을 그룹핑하는 역할이다. 그룹핑되어 있기 때문에 다른 작업을 호출하다가 실패하면 전체가 취소 처리된다.
CoroutineScope(Main).launch{
//do something
}
CoroutineScope(IO).launch{
//do something
}
CoroutineScope(Default).launch{
//do something
}
코루틴 컨텍스트에는 Main, IO, Default로 세 가지가 있다.
- Main은 메인 스레드에 대한 컨텍스트로, UI 갱신이나 Toast 등의 view 작업에 사용된다.
- IO는 네트워킹이나 내부 DB접근 등 백그라운드에서 필요한 작업을 수행할 때 사용된다.
- Default는 크기가 큰 리스크를 다루거나 필터링을 수행하는 등의 무거운 연산을 할 때 사용된다.
suspend
임의의 api와 통신한 뒤성공 여부를 반환 받는 getResultFromApi 라는 함수가 있다고 하자. suspend함수다.
const val RESULT_OK = "ok"
suspend fun getResultFromApi(): String{
//do something
return RESULT_OK
}
suspend함수는 그 함수가 비동기 환경에서 사용될 수 있다는 의미를 내포하고 있다.
suspend함수는 비동기 함수이고, [다른 suspend함수 or 코루틴 내] 에서만 호출할 수 있다. 다른 곳에서 호출하면 warning 뜨면서 아래 메시지가 난다.
Suspend function (FUNCTION_NAME) should be called only from a coroutine or another suspend function
delay
코루틴에서 정의된 suspend function으로, 코루틴이나 다른 suspend함수 안에서만 수행될 수 있다. Thread.sleep(몇초)와 비슷한 기능을 하는데 코루틴은 하나의 스레드 속에서 돌아가는 하나의 job이고, 이 하나의 스레드 안에 여러 개의 코루틴이 실행되고 있을 수 있다. 그렇기 때문에 delay는 한 스레드 속에 있는 여러 코루틴 중에 코루틴 하나만 멈춘다. 반면 Thread.sleep은 스레드 하나를 멈추니까 그 속에 있는 여러 개의 코루틴을 모두 멈춘다. 그러니 코루틴 내부에서 스레드 슬립을 호출하면...별로 안 좋다!
withContext
CoroutineScope(IO).launch{
val resultStr = getResultFromApi()
textView.text = resultStr
}
IO Context에서 네트워크 통신을 하는 것까지는 괜찮으나 텍스트를 설정하는 부분은 메인 스레드에서 작업해야 한다. 그래서 위의 코드는 크래쉬가 발생한다. 아래와 같이 고칠 수 있다.
CoroutineScope(IO).launch{
val resultStr = getResultFromApi()
CoroutineScope(Main).launch{
textView.test = resultStr
}
}
근데 이대로 두면 Main Context를 가지는 또다른 코루틴을 생성하게 되는 것이다. 코루틴을 굳이 하나 더 생성할 필요가 없다. 리소스 낭비일 뿐.... 이때 사용하는 것이 withContext다. withContext를 사용해서 바꿔보면 아래와 같다.
CoroutineScope(IO).launch{
val resultStr = getResultFromApi()
withContext(Main){
textView.text = resultStr
}
}
withTimeoutOrNull
네트워크 타임아웃 처리를 할 때 사용한다. 명시한 밀리세컨트를 초과하는 시간이 걸리는 경우 null을 반환한다.
CoroutineScope(IO).launch{
val resultStr = withTimeoutOrNull(10000) {
getResultFromApi()
}
if(resultStr != null){
withContext(Main) {
textView.text = resultStr
}
}
}
출처
https://wooooooak.github.io/kotlin/2019/06/28/coroutineStudy2/
'Tech > Kotlin & Coroutine' 카테고리의 다른 글
코틀린 시작에 앞서 이것저것 (2) | 2024.02.09 |
---|---|
코루틴(coroutine)에 대하여. 3편. 코루틴을 cancel하자 (1) | 2021.07.24 |
코루틴(coroutine)에 대하여. 2편. 코루틴을 실행하자 (1) | 2021.07.23 |