[Kotlin] 코루틴(Coroutine)이란? - CoCurrency 와 비동기 프로그래밍
0. 코루틴 Coroutine 과 비동기 프로그래밍
coroutine 은 비동기 처리를 하는데 사용되는 동시 실행 디자인 패턴(방법론에 가깝다)이다. 코틀린과 이름이 비슷하지만, 코틀린 뿐만 아니라 파이썬, C#, Go, Javascript 등 여러 언어에서 지원하고 있는 개념이다.
Android 에서는 비동기 프로그래밍을 위한 방법으로 coroutine 을 제안한다.
멀티 스레딩을 처리할 수 있는 현재의 프레임 워크는 thread-safe excution 을 보장하는 간단한 방법이 없기 때문에, 그 이름도 유명한 콜백 지옥을 마주치게 된다. 나는 retrofit 을 이용한 서버와의 통신 과정에서 콜백 지옥이라는 것에 대해 알게 되었다. 처음엔 이 "비동기" 라는 개념을 제대로 이해하지 못해서 레트로핏을 이용해 서버와 통신하는 비동기 코드가 기존의 다른 코드들과 같이 "동기"적으로 실행된다고 생각했다. 그래서 안드로이드 오픈채팅방에 레트로핏을 사용해서 return 값을 받고 싶다는 바보같은 질문을 한 적도 있었다. 지금 생각하면 정말 부끄러운 질문이지만, 직접 부딪혀 보면서 비동기란 무엇인가에 대해 이해할 수 있었던 좋은 경험이었다고 생각한다.
아무튼, 코루틴이라는 프레임워크를 사용하면 효율적이고, 간단한 방법으로 동시성(병렬성과 다른 개념임을 주의)을 관리할 수 있다고 한다.
사실 이 "간단한" 방법이라는 것은 상대적인 것이다. 안드로이드는 코루틴 이외에도 비동기 처리를 할 수 있는 여러 API 들이 있다. 그 중 대표적인 것이 Handler 와 AsyncTask 이다. 그러나, 안드로이드에서는 Thread 프로그래밍의 위험성 때문에 AsyncTask 를 deprecated 시켜버렸다. 이로 인해 안드로이드 개발자들에게는 비동기 처리를 위한 ReactiveX 라이브러리(RxJava, RxKotlin) 가 거의 필수처럼 대두되고 있다. 그러나, ReactiveX 는 러닝커브가 매우 매우 높고, 어렵다. 그러나 Coroutine 은 ReactiveX 에 비해 간단하고 가독성 높게 비동기 처리를 구현할 수 있다.
1. Coroutine 단어의 의미
coroutine 은 co 와 routines 의 합성어이다.
co 는 cooperation, "협력하다" 라는 뜻이고, routines 는 funtion 을 의미한다.
routine 은 Subroutien 과 Coroutine 으로 나뉜다. 코드를 짜다보면, 중복되는 부분을 함수로 만들어 호출하며 사용하는데, 여기서 말하는 Subroutine 이 바로 그 함수를 말하는 것이다. Coroutine 은 Subroutine 의 확장개념으로 "함수들이 협력하는 것" 이라고 직역하여 해석할 수 있다. 즉, coroutine 이란 여러 함수들이 번갈아가며 실행되는 비동기적인 프로그래밍 개념이라고 생각 할 수 있다.
2. Coroutine 을 사용하는 이유
안드로이드에서 메인 쓰레드는 UI 쓰레드이다. 그렇기 때문에 메인 쓰레드에서 UI 를 그리는 것 이외에 복잡하고 시간이 오래 걸리는 작업을 수행하는 것은 적합하지 않다. 복잡한 다른 작업이 끝날 때까지 시스템은 사용자에게 보여질 UI 를 그리지 못하고, 사용자는 영문도 모른채 하얗게 빈 화면만을 보며 기다려야 한다. 그래서 안드로이드에서는 그러한 작업들을 메인 쓰레드에서 하지 못하도록 한다. 그러한 복잡한 작업의 가장 대표적인 예로, 네트워크 작업이 있다. 만약 네트워크 작업을 메인 쓰레드에서 진행하려고 하면 NetworkOnMainThreadException 이 발생하게 된다.
사용자의 데이터를 서버로부터 가져와서 화면에 보여주는, 안드로이드 앱에서 매우 흔한 유즈 케이스를 예로 들어 생각해보자. 이러한 경우 다음과 같이 코드를 작성할 수 있겠다.
fun fetchUser(): User {
// 네트워크 통신을 통해 서버로부터 유저 데이터 가져옴
// 유저 데이터를 리턴
}
fun showUser(user: User) {
// 유저 데이터를 화면에 보여줌
}
fun fetchAndShowUser() {
val user = fetchUser()
showUser(user)
}
위와 같이 코드를 작성한 뒤, fetchAndShowUser() 함수를 호출하면, NetworkOnMainThreadException 이 발생한다. 이러한 문제를 해결하기 위해 사용되는 것이 바로 비동기 프로그래밍이다. 메인 쓰레드는 그대로 동작하면서, 메인 쓰레드 외의 백그라운드 쓰레드에서 네크워크 작업을 수행하는 것이다. 안드로이드에서 비동기 작업을 수행하는 방식에는 여러가지가 있는데, 그 중 많이 사용되는 방법은 다음과 같다.
1. Callback 사용하기
네트워크작업을 하는 fetchUser 작업을 백그라운드 쓰레드에서 동작시키고, fetchUser 의 결과를 callback 을 통해 showUser 에게 전달한다.
fun fetchAndShowUser() {
fetchUser { user ->
showUser(user)
}
}
2. RxJava 사용하기
fetchUser()
.subscribeOn(Schedulers.io())
.observerOn(AndroidSchedulers.mainThread())
.subscribe { user ->
showUser(user)
}
3. Coroutine 사용하기
suspend fun fetchAndShowUser() {
val user = fetchUser() // IO 쓰레드(네트워크, 디스크를 관련 작업시 사용하는 쓰레드)
showUser(user) // UI 쓰레드로 다시 돌아옴
}
1번 callback 과 같은 경우, 예전부터 비동기적 프로그래밍 방식에서 굉장히 많이 사용하는 방식이긴 하나, 이른바 "콜백 지옥"이라는 것에 빠지게 되면 코드 가독성이 상당히 떨어지고 유지보수의 측면에서도 좋지 않다.
2번 RxJava 의 경우, 콜백을 사용하는 방식보다는 좀더 정돈된 코드를 볼 수 있다. RxJava 또한 Coroutine 과 같이 비동기 처리를 위해 개발자들이 굉장히 많이 사용하는 API이며, 높은 효율성과 장점을 가지고 있다. 그러나, RxJava 는 러닝커브가 굉장히 높고 어려운 API 이다. 또한, Coroutine 은 RxJava 와는 다른 특징이 있다. 그것은 바로, Coroutine 은 멀티 태스킹이지만, 멀티 스레딩(메인 스레드 이외의 백그라운드 스레드에서 작업하는 것)이 아니라는 것이다.
멀티 스레딩 작업의 경우, 스레드가 OS 를 통해 관리(context switching)되기 때문에 CPU 에서의 비용이 많이 든다. 그러나 Coroutine 의 경우, 작업들이 동시에 진행되는 것처럼 보이지만 사실은 하나의 스레드에서 동기적으로 작업이 진행되는 것이다. 이것을 Cocurrency(동시성)이라 부른다. 그렇기 때문에 스레드가 아무것도 하지 않는 유휴 상태를 거의 방지하여 스레드를 최대한 활용할 수 있게 된다. 또한, 멀티 스레딩 기법에서 CPU 를 많이 소모하는 컨텍스트 전환이 필요하지 않기 때문에 속도가 훨씬 빠르다.
이러한 이유로 비동기 프로그래밍 방식을 구현하기 위해 Coroutine 을 사용한다.
3. 안드로이드에서 Coroutine 을 사용하는 방법
https://codelabs.developers.google.com/codelabs/kotlin-coroutines/#0
참고 사이트>>>
https://blog.mindorks.com/mastering-kotlin-coroutines-in-android-step-by-step-guide
https://github.com/googlecodelabs/kotlin-coroutines.git