왓챠 과제전형을 진행하면서, 코루틴을 사용하는 도중에 lifecycle 과 coroutine 동작에 대한 학습이 부족함을 느꼈습니다.

그래서 다시한번 Coroutine 에 대해 정리를 하고자 합니다.

 

코루틴은 코틀린때문에 등장한 것이 아니라고 합니다.

코루틴의 개념은 훨씬 이전부터 존재해왔으며 (언제부터일까... 그것까진 안찾아봤어요.. )

오히려 저같은 후세대 개발자들은 안드로이드에서 코틀린을 공식언어로 적용하면서 만났을 것이라고 생각합니다.

 

 

1. MainRoutine 과 SubRoutine

코루틴에 대해서 알기 전에, 메인루틴과 서브루틴에 대해서 알아야 합니다.

메인루틴은 흔히, 메인메소드라고 생각하면 됩니다.

서브루틴은 메인함수 내에서 동작하는 메소드라고 생각하시면 됩니다.

 

    private fun mainRoutine() {
        val nameA = subRoutine1()
        val nameB = subRoutine2()
        val nameC = subRoutine3()
    }

    private fun subRoutine1() : String {
        return "subRoutine1"
    }
    private fun subRoutine2() : String {
        return "subRoutine1"
    }
    private fun subRoutine3() : String {
        return "subRoutine1"
    }

위 코드는 mainRoutine() 에서 순차적으로 subRoutine1() -> subRoutine2() -> subRoutine3()

으로 동작합니다.

 

순차적으로 동작하는 만큼, subRoutine1() 에서 while(true) 에 break; 가 없다면, subRoutine2() 과 subRoutine3()은 

절대 동작할리가 없겠죠? 만약 while 탈출 조건이 있다고 해도, 탈출하기 전까지는 다음 코드가 실행되지 않습니다.

 

이게 왜 중요한가???? 

Thread 동작

하나의 쓰레드 내부코드는 위에서 설명한 것처럼 모두 순차적으로 진행됩니다.

멀티쓰레드는 내부코드가 여러개로 나뉘는 것이 아닌, 순차적으로 진행되는 각 쓰레드를 여러개 두고 돌아가며 실행시키는 것이라고 보면 됩니다.

 

 

Routine 이 무엇인지 알았나요?

MainRoutine 내부에 SubRoutin, 그 안에 SubRoutine, 그 안에 SubRoutine ... 이렇게 존재한다고 하면,

결국 마지막 SubRoutine 의 코드부터 끝이 나야 그 다음 Soutine 들이 종료가 가능한거죠.. 음?.. 어...?

스택이 떠오르네요. Routine 은 스택으로 동작하는 군요!

 

어찌보면, 당연한 흐름이겠습니다만, 이런식으로 깊게 깊게 들어가면 끝이 없겠...네요... 이친구는 여기까지!

코루틴은 무엇일까?

코루틴은 시작부터 종료까지 suspend 와 resume 이 여러번 호출될 수 있습니다.

이게 무엇이냐면,

subRoutineA 가 실행된중에 잠시 suspend 하여 탈출하고, subRoutineB 를 실행하다가 subRoutineB 를 끝내거나,

subRoutineB 도 잠시 중지한다음 subRoutineA 를 다시 실행할 수 있다는 것입니다.

 

* 핵심은 return 이 아니기 때문에 subRoutineA 가 중지된 부분부터 이어서 실행이 가능하다는 것이죠!

 

    private fun mainRoutine() {
        subRoutine1()
        subRoutine2()
        subRoutine3()
    }

    private fun subRoutine1() {
        var count = 0
        while(count<5) {
            Thread.sleep(500)
            println("subRoutine1")
            count += 1
        }
    }

    private fun subRoutine2() {
        var count = 0
        while(count<5) {
            Thread.sleep(500)
            println("subRoutine2")
            count += 1
        }
    }

    private fun subRoutine3() {
        var count = 0
        while(count<5) {
            Thread.sleep(500)
            println("subRoutine3")
            count += 1
        }
    }

이 코드는 코루틴이 아닌 일반적인 코드입니다. 해당 코드들은 어떻게 동작할지 상상이 가죠?

 

그렇다면 다음 코드를 볼까요?

    private fun mainRoutine() {
        MainScope().launch {
            println("Start subRoutine1")
            subRoutine1()
        }

        MainScope().launch {
            println("Start subRoutine2")
            subRoutine2()
        }

        MainScope().launch {
            println("Start subRoutine3")
            subRoutine3()
        }
    }

    private suspend fun subRoutine1() {
        var count = 0
        while(count<5) {
            delay(500)
            println("subRoutine1")
            count += 1
        }
    }

    private suspend fun subRoutine2() {
        var count = 0
        while(count<5) {
            delay(500)
            println("subRoutine2")
            count += 1
        }
    }

    private fun subRoutine3() {
        var count = 0
        while(count<5) {
            Thread.sleep(500)
            println("subRoutine3")
            count += 1
        }
    }

subRoutine1, 2, 3 은 모두 코루틴으로 실행을 시켰습니다.

 

그럼, 1,2,3,1,2,3,1,2,3,1,2,3,1,2,3, 순으로 돌까요?

 

정답은 틀렸습니다.

 

subRoutineA 가 실행된중에 잠시 suspend 하여 탈출하고, subRoutineB 를 실행하다가 subRoutineB 를 끝내거나,

subRoutineB 도 잠시 중지한다음 subRoutineA 를 다시 실행하려면

 

suspend 로 메소드를 작성해야합니다.

 

즉, subRoutineA 와 subRoutineB 는 서로 잠시 탈출하고, 다음대상실행, 탈출 , 실행 으로 동잡합니다만,

 

subRoutineC 는 suspend 키워드가 없기 때문에  선점한 이후에 탈출하려면 subRoutineC 가 종료되어야만 가능합니다.

 

따라서 실행결과는 아래와 같습니다.

 

이러한 동작은 멀티쓰레드로 만들어낼수도 있습니다.

그러나, 엄청나게 큰 차이는

 

멀티쓰레드 : 쓰레드간 컨텍스트 스위칭으로 서로 다르게 동작

코루틴 : 단일쓰레드에서 함수를 잠시 일시정지하고 다른 함수를 실행하는 식으로 동작

 

Suspend 메소드는 어떤식으로 동작할까?

 

아래와 같은 suspend 메소드를 변경시켜보겠습니다.

정확하게 suspend 가 변하는 부분은 훨씬 복잡할 것입니다.

어떤 메소드를 사용할지 결정하는 등에 대한 부분도 고려가 될것으로 판단되구요..

중요한건 결국 메소드를 스위칭 한다는 것이죠

즉, 1과 2가 서로 어떻게 돌아가면서 실행되어야 하는데요,

 

따라서 아래와 같이 변경해 보았습니다.

label 을 두고, 현재 메소드 실행 이후에 어떤 메소드를 실행할기 결정시키는 것인데요, 공부한바에 의하면

코루틴의 suspend 는 저런식으로 label 가지고 결정한다고 합니다. 물론 훨씬 복잡하겠지만요 :)

 

멀티쓰레드 vs 코루틴  누가 좋을까?

 

멀티쓰레드를 스위칭하는 작업은 코루틴에서 함수를 스위칭하는것보다 훨씬 많은 메모리를 필요로 합니다.

즉, 사용 메모리 : 멀티쓰레드 > 코루틴

 

그러나, 코루틴은 단일쓰레드 내에서 여러개를 번갈아가며 실행시키는 것입니다.

(이는 단일코어에서 멀티쓰레드를 번갈아가며 실행시키는 것과 유사하다고 봐야할것 같습니다.)

 

즉 10개의 Task 가 있다면,

 

1. 단일코어 멀티쓰레드 : 1개의 코어에 10개의 쓰레드를 동작시키고...

  - 처리속도

멀티쓰레드의 컨텍스트 스위칭 시간 vs 코루틴 labeling 시간 중 누가 더 빠른가?

를 봐야할 것 같습니다. 개인적으로는 최소 같거나, 코루틴이 더 빠를것이라고 생각합니다.

 

2. 멀티코어 멀티쓰레드 : 3개의 코어가 3,3,4 의 쓰레드를 동작시키고...

 - 처리속도

멀티코어 멀티쓰레드 > 코루틴

 

그러나, 이 부분은 좀더 깊이있게 공부해서 어떤것이 좋은지 확인이 필요할것 같습니다.

제가 아직 이 판단은 잘 서지 않네요.. 

그러나, 쓰레드를 정말 제대로 잘짜는게 아니라면 코루틴이 좋지 않을까 싶...흡...

GlobalScope vs MainScope vs lifecycleScope 등등등

안드로이드에서 코루틴을 사용하다보면,

 

~~~Scope.launch {

...

}

 

이런식으로 많이 사용을 하게 되는데요.

대체 어떤 스코프를 써야하는지에 대한 생각이 들었던 적이 있었습니다. 다시한번 코루틴을 정리한 이유도 이때문이구요.

 

1. GlobalScope : 자체적인 쓰레드풀을 하나 갖고 있는 곳에서 실행됩니다.

즉 정말, Global 한 영역에서 동작시킨다고 봐야합니다.

MainActivity 에서 호출한다면, Application 이 해당 코루틴의 동작구간이 된다고 봐야합니다.

 

2. MainScope : MainThread 에 해당하는 영역에서 실행됩니다.

MainActivity 에서 호출한다면, MainActivity 가 해당 코루틴의 동작구간이 된다고 봐야합니다.

 

3. lifecycleScope : 호출되는 라이프사이클에 맞게 실행됩니다.

 

NullPointException 의 고통을 받고 싶지 않다면, 사용되는 코루틴이 LifeCycle 에 맞게 생성, 제거 될 수 있도록 신경을 써야 할 것 같습니다.

 

즉, 저는 앞으로 앱을 개발할 때, lifecycleScope 를 제일 많이 사용하게 될 것 같고, 그러지 못하는 경우에는 cancel 을 적절하게 사용하고, 제대로 동작할 수 있도록 고민하며 개발을 진행해야 한다고 판단했습니다.

+ Recent posts