Tech Log

[Android] Coroutine 개념 본문

Android/Kotlin

[Android] Coroutine 개념

yuhee kim 2022. 5. 11. 19:15

네트워크 통신을 하면서 동기와 비동기에 대해서 알게 되었다. 비동기를 처리하는 기술 중 Coroutine이 잘 쓰이고 있다는 것을 알게되어, 프로젝트에 적용도 해보았다. 그리고 Coroutine에 대해서 자세히 알아보고 싶어 조사해보았다.

1. Coroutine의 정의

Coroutine 이라는 단어 자체를 보면 알수있듯이, Coroutine은 Co + routine의 합성어이다. 직역하면 '협동(같이하는) 루틴'이 된다.
Android Developers에 따르면, 비동기적으로 실행되는 코드를 간소화하기 위해 Android에서 사용할 수 있는 동시 실행 설계 패턴이라고 한다. 필자는 사실 이 설명을 보고 Coroutine이 무엇인지 이해할 수 없었다.
Coroutine을 이해하려면, 먼저 동기와 비동기가 무엇인지 알아야 한다.

2. 동기와 비동기

출처 : 벨로퍼트와 함께하는 모던 자바스크립트(https://learnjs.vlpt.us/async/)

그림을 참고해보면, 동기는 서버에게 데이터를 요청했을 때 응답이 오기 전까지 다른 일을 할 수 없다.
반면 비동기는 서버의 응답이 오지 않았더라도 다른 일을 할 수 있다.

동기를 사전적 의미로 접근하면, 같은 시기라는 의미가 내포된다.
이때의 같은 시기는 같은 시기에 여러 가지 일을 하는 것을 말하는 것이 아니라,
지금 데이터 요청에 대한 서버 응답이 오는 시기와 이 다음에 할 데이터 요청동시에 일어나기 때문에 동기라고 불리는 것이다.
반면에 지금 데이터 요청에 대한 응답과 다음에 할 데이터 요청이 일어나는 시기가 동일하지 않는 것이 비동기이다.

그럼 여기서 동기/비동기와 Coroutine의 관계가 무엇인지 궁금해질 수 있다. Coroutine을 설명하는데 왜 동기/비동기에 대한 설명이 필요할까?
1. Coroutine의 정의에서 설명했듯이, Coroutine이라는 단어의 뜻을 직역하면 협동(함께하는) 루틴이다.
또한, Android Developers가 정의하는 바에 따르면 동시 실행 설계 패턴이라고 한다.
위 두 가지 정의에서 공통되는 것이 있다. 협동, 동시 이 두가지 모두 '같이한다'는 의미를 내포하고 있다.
즉, Coroutine은 동시에, 함께 일이 진행되는 것을 처리하는 패턴이다. 비동기를 처리하는 패턴이라는 의미도 된다.

3. 동시성과 병렬성

그렇다면, Coroutine은 어떻게 일을 동시에 처리할까? 동시에 일을 진행하려면 일반적으로 멀티스레드를 사용하는 방법이 있다.
하지만 대다수의 경우 Coroutine으로 멀티스레드는 거의 사용하지 않는다. 멀티 스레드와는 다른 방식으로 많이 사용이 되는데, 이 다른 방식을 이해하려면 동시성과 병렬성이라는 것을 알아야 한다.

출처 : 동시성(Concurrency) 과 병렬성 (Parallelism) 올바른 개념 잡기(https://vagabond95.me/posts/concurrency_vs_parallelism/)


위 그림을 참고하면서 아래 설명을 읽어보자.
Case 1은 동시성을 만족하지만 병렬성은 만족하지 않는 경우다.
Case 2은 동시성은 만족하지 않지만 병렬성은 만족하는 경우다.

간단하게 말하자면, 동시성은 2개 이상의 task가 있을 때 서로 다른 task의 실행 시점에 상관없이 task 실행이 가능하다는 의미를 가진다.
다시 말해, 2개 이상의 task가 앞의 task를 기다리지 않고, 필요하다면 앞의 task를 중단시키고 다른 task를 할 수 있다는 의미가 된다. 싱글 코어의 경우이므로 물리적으로 같은 시간 내에 여러 개의 task를 동시에 진행할 수는 없다.
병렬성은 병렬성은 2개 이상의 task 가 있을 때 각 task 가 물리적인 시간으로 동시에 실행이 가능하다는 의미를 가진다.
이는 물리적으로 같은 시간 내에 여러 개의 task가 동시에 진행된다는 의미이다. 멀티 코어이므로 가능한 것이다.
사실 Coroutine은 동시성을 만족하는 경우에 많이 사용되고, 병렬성을 만족하는 방법에는 상대적으로 적게 사용된다.

동시성과 병렬성에 대해서 자세히 알아보고 싶으면 아래 링크를 참조하는 것이 좋은 방법이 될 것이다.

동시성과 병렬성이란

Coroutine은 일반적으로는 동시성은 만족하지만 병렬성은 만족하지 않는 경우이므로 Case 1에 해당된다.
Coroutine을 사용하면 여러 task를 왔다갔다(?)하면서 진행할 수 있다(병렬성을 만족시키면서 멀티 스레드로 Coroutine을 사용할 수도 있긴 있다)

4. Coroutine의 원리

배경 설명이 길었지만, 그렇다면 Coroutine은 어떻게 왔다갔다(?) 진행할 수 있는 것일까?(동시성 기준)

    fun main(){ //Main routine
        var value = 10
        value = addValue(value)
    }
    private fun addValue(value : Int) : Int { //Sub routine
        return value + 10
    }


먼저 Routine에는 Main routine과 Sub routine이 존재한다.
위 사진에서 main 함수가 Main routine이다. main에서 호출되는 addValue라는 함수가 Sub routine이 된다.
Sub routine인 addValue 함수의 경우, 진입점빠져나오는 지점이 명확하다.
main 함수에서 addValue가 호출될 때가 진입점이 되고, addValue 함수 내에서 return을 만났을 때 빠져나오는 지점이 된다.
이 경우가 가장 일반적인 경우일 것이다.

그러나, Coroutine을 사용하게 되면 Sub routine의 진입점과 빠져나오는 지점이 여러 개가 된다.
그렇기 때문에 return 문이나 닫는 괄호를 만나지 않더라도 언제든지 나갈 수 있다.
혹은 나갔던 지점으로 다시 돌아올 수 있다.

    fun drawPerson(){
        startCoroutine {
            drawHead()
            drawBody()
            drawLegs()
        }
    }

    suspend fun drawHead(){
        ...
    }

    suspend fun drawBody(){
        ...
    }

    suspend fun drawLegs(){
        ...
    }

drawPerson이라는 함수가 있고 그 안에 startCoroutine이라는 Coroutine 빌더가 있다. 물론 startCoroutine은 실제로 존재하는 빌더가 아니라, 임의로 만들어낸 것이다.
startCoroutine으로 Coroutine이 만들어지면서 본격적인 비동기 과정이 실행된다.

1. startCoroutine 내의 drawHead라는 함수가 불리면서, 하나의 Coroutine 함수가 만들어진다.
이때 drawHead의 진입점과 빠져나오는 지점은 정해져 있지 않고 언제든지 들어오거나 빠져나올 수 있다(Coroutine 빌더 내에서 함수가 호출되었으므로)
여기서 drawHead()는 suspend라는 키워드를 갖고 있는 함수다.
startCoroutine으로 Coroutine이 빌드된 다음에 suspend 함수가 호출되면, 현재 실행되고 있는 Coroutine 함수를 빠져나와 suspend 함수로 넘어간다.

2. 메인 스레드에서는 drawHead()를 제외하고 다른 코드들이 실행된다.

3. drawHead()는 다른 곳에서 계속 그려지고 있다(이는 다른 스레드에서 돌아가는 것일 수도 있고 메인 스레드에서 동시성 프로그래밍으로 돌아가는 것일 수도 있다. 이는 개발자의 선택 사항이다)

4. drawHead()가 끝나면 drawHead()로 진입해서 Main routine에서 멈췄던 부분으로 다시 돌아온다.

5. drawBody()를 호출한다.
drawHead()와 같이, suspend 함수이므로 이전과 같은 과정을 반복한다.

6. Main routine에서 호출되는 다른 함수들도 다 suspend 함수이므로 이전 과정이 반복된다.


Coroutine이라는 개념이 추상적으로 느껴져서 정리를 해보았다.
처음 이 개념을 알게 되었을 때는 어떨 때 쓰는 것인지 감이 오지 않았다.
개념에 대해 이해가 되지 않는다면 공식 문서를 먼저 확인해보고, 신빙성 높은 블로그 글들을 찾아 여러 개 읽어보는 게 좋을 것 같다.

Coroutine은 동시성을 다루는 도구이므로 제대로 익히고 써야, 데이터의 손실을 막을 수 있다고 한다.
또한 제대로 예외처리하지 않거나, 메인 스레드가 오래 블로킹되고 있을 경우 ANR(Applicaiton Not Responding)을 유발할 수 있다.
이렇기 때문에 더욱이 충분히 공부하고 사용해야 할 것 같다.

이후에는 Coroutine 사용 방법에 대해서도 작성해봐야 겠다.

참조

'Android > Kotlin' 카테고리의 다른 글

[Kotlin] 코틀린 언어 특징  (0) 2022.06.27
[Android] View Binding  (0) 2022.04.06
[Android] 안드로이드 액티비티 생명주기  (0) 2022.03.02
Comments