왓챠 과제전형을 진행하면서, 코루틴을 사용하는 도중에 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 을 적절하게 사용하고, 제대로 동작할 수 있도록 고민하며 개발을 진행해야 한다고 판단했습니다.
'BackUp (관리중지) > Android 이론' 카테고리의 다른 글
이제는, SharedPreferences 👉 DataStore (0) | 2021.05.16 |
---|---|
Android Service (0) | 2021.05.14 |
[Android Jetpack] LiveData란, (0) | 2021.04.21 |
Fragment 와 Fragment LifeCycle 분석 (0) | 2021.04.19 |
Activity 와 Activity LifeCycle 분석 (0) | 2021.04.15 |