[Android | Kotlin] Coroutine과 Flow 정리하기(2) - Coroutine

한시삼십사분·2023년 7월 31일
1

Android

목록 보기
5/10
post-thumbnail

🧐도입

Thread에 대해 간단히 알아봤고, coroutine에 대해 공부해려고 한다.
지금까지 대충이나마 coroutine을 사용했을 때는 flow와 함께 썼는데, 왜 이렇게 사용해야하나에 대한 고민은 없이 써왔기 때문에 flow를 배제하고 coroutine에 대해 공부하려니 크게 와닿지가 않아 조금 힘들었다.
하지만 최대한 내가 이해한 것을 바탕으로 작성해보겠다. 물론 아주 많이 틀리겠지만 나중에라도 더 공부하다가 틀린 부분이 생각나면 수정하겠다.

코루틴이 스레드인건가?

난 안드로이드에서 코루틴을 쓰면서 그냥 요즘 안드로이드 진영에서 밀고있는 스레드 라이브러리...? 같은 것인가 하고 사용했다. 그리고 코루틴을 검색해보면 "경량 스레드"라는 말을 가장 쉽게 접할 수 있었다.

그렇다면 코루틴은 스레드일까?

우선 스레드와 코루틴은 둘 다 동시성 프로그래밍을 통해 비동기적 프로그래밍을 위한 수단이다. CPU가 A스레드를 처리하다가, 중간에 갑자기 B스레드를 처리하다가... 여러 스레드를 나름의 방식으로(운영체제 프로세스 스케줄링 알고리즘 시간에 배운 여러 방법들) 처리하는 것을 반복하며 작업을 수행한다.

하지만 다들 알다시피 스레드는 context switching에서 많은 자원을 소모한다. 또한 스레드가 다른 스레드를 기다리는 동안 다른 작업을 수행할 수 없어 자원의 낭비가 발생한다.

코루틴은 한 스레드 안에서 여러 코루틴을 동시성 프로그래밍을 통해 처리된다. 그리고 코루틴간의 전환에는 context switching이 발생하지 않으며, 스레드가 block되며 발생하는 자원의 낭비도 발생하지 않는다.

즉 코루틴은 스레드 안에서 처리되는 더 작은 스레드 처럼 생각할 수 있다. (표현이 적합한지는 모르겠다...)

코루틴의 생성

앞서 살펴본 것 처럼 java에서 thread를 생성, 실행하려면 thread를 상속받은 객체에서 run 함수를 정의하고 start함수를 통해 실행했다.

코루틴은 어떻게 생성하고 실행할까?

launch와 async

코루틴은 launch와 async로 생성되는 코루틴 블록을 통해 생성된다.
둘의 차이는 launch는 특정 값을 반환하지 않지만 async는 특정 값(async 블록 마지막 줄의 값)이 Defferd로 감싸져 반환된다는 것이다, 또한 값을 얻기 위해 await()를 사용한다.

또한 하나의 코루틴 블록 내에서 여러개의 async를 실행시켜 병렬적인 프로그램을 만들 수 있다.

CoroutineScope.launch(Dispatchers.Default){
	//code
}

CoroutineScope.async(Dispatchers.Default){
	//code
    ...
    ...
    something to return
}

//async를 활용한 병렬 프로그래밍 예시
CoroutineScope.launch(Dispatchers.Default){
	val sum: Deffered<Int> = async{
    	//do something
        sumNumbers()
    }
    
    val minus: Deffered<Int> = async{
    	//do something
        minusNumbers()
    }
    // 각각의 코루틴이 완료된 시점에 await 호출
    println(sum.await() + minus.await())
}
  • Dispatchers

코루틴에는 Dispatcher가 있다. Dispatcher의 역할은 자기가 관리하고 있는 스레드풀 중 현재 적합한 스레드에 코루틴을 배정해 주는 것이다.

예를 들어 위 코드에서의 Dispatchers.Default일 때 Defulat Dispatchers가 관리하는 스레드풀 중 작업을 수행하기 가장 적합한 스레드에 코루틴을 배정시켜준다.

코루틴에서 Dispatchers는 3가지가 있다.

  • Dispatchers.Main
    : 메인 스레드에서 코루틴을 실행시킨다. 안드로이드에서 가장 우선적인 사용자와의 UI적 상호작용을 위해서 사용되어야 한다.
  • Dispatchers.Default
    : 자원을 많이 소모하는 무거운 작업을 실행시키기에 적합하다. (ex. 정렬)

Dispatchers.IO
: 네트워크 또는 로컬에서 데이터를 저장, 가져오기에 적합하다.

  • Scope

위의 코드에서는 CoroutineScope에서 코루틴이 실행된다.
코루틴의 scope는 코루틴이 실행되고 취소되는 생명주기와 같다고 생각하면 될 것 같다.
코루틴은 scope 내에서만 동작한다!!
예를 들어 viewModelScope에서 실행되는 코루틴은 viewmodel과 생명주기를 같이 한다. viewmodel의 생명주기에 맞춰 실행되고 취소된다.

대표적인 몇가지 scope다.

  • GlobalScope
    : 앱의 실행부터 종료까지 실행된다. 즉 심각한 자원의 낭비가 발생할 수 있기 때문에 사용에 매우 주의를 요한다.(그냥 안쓰는게 최고인듯)
  • coroutineScope
    : 이부분이 이해하기 가장 어려웠는데, 적당한 시점에 명시적인 cancle을 해주면서 메모리 누수를 방지해야하는 scope인 것 같다. 세심한 관리가 필요해보인다.
  • viewModelScope
    : viewmodel의 생명주기상에서 실행된다. viewmodel에서 코루틴을 생성할 떄 사용하면 된다.
  • lifecycleScope
    : 액티비티, 프래그먼트 등의 라이프사이클에 맞춰 실행된다. 대상이 destroy 될 때 코루틴이 취소된다.

withContext(Dispatcher)로 부모에게서 넘어온 Dispatcher를 변경할 수 있다.

이제 다시 위의 코드를 보면 이해가 된다!

CoroutineScope.launch(Dispatchers.Default){
	//code
}

앱의 전체 실행부터 종료까지 실행되는 CoroutineScope에서 Default Dispatchers로 코루틴 작업을 실행하라는 코드다.

✍️ 결론

오늘 알아본 부분은 코루틴의 핵심이지만 일부이다. 코루틴도 계속해서 발전하고있고, 더 좋은 기능들이 추가되었다.
하지만 개인적으로 안드로이드 공부를 하는 입장에서 "그래서 안드로이드에서 어떻게 왜 사용하고있는데?"라는 의문이 깔끔하게 해소되지는 않아 크게 와닿지가 않는다.

그렇기 때문에 다음 글에서는 안드로이드에서 코루틴과 항상 함께 등장하는 Flow에 대해 공부하고, 코루틴을 왜, 어떻게 사용하는지에 대해 공부해보겠다.

profile
인간은 망각의 동물이라지만 이건 너무한 거 아니냐고

2개의 댓글

comment-user-thumbnail
2023년 10월 24일

정말 유익합니다. 선생님 덕분에 코루틴을 마스터 했습니다.

1개의 답글