반응형
안드로이드에서 프로세스
- 애플리케이션이 시작되면 안드로이드 시스템은 애플리케이션을 위한 새로운 프로세스와 스레드를 생성한다.
- 기본적으로 애플리케이션 안의 모든 컴포넌트들은 동일한 프로세스의 동일한 스레드에서 실행한다.
- 이 기본적인 스레드를 메인 스레드라고 하며, 메인 스레드는 위젯에게 이벤트를 전달하거나 화면을 그리는 등 UI 관련 모든 작업을 담당한다.
문제점
- 애플리케이션에서 많은 작업을 해야 한다면 이와 같은 단일 스레드 모델은 애플리케이션의 성능을 저하시킨다.
- 대기 시간이 길어지는 네트워크 요청 등의 기능을 수행할 때는 화면에 보이는 UI도 멈춤 상태인 ANR(Application Not Responding) 오류가 발생한다.
- 해결방안으로는 하나의 프로세스 안에서 여러 개의 작업을 동시에 할 수 있는 멀티 스레드 방식을 채택하는 것이다.
- 그러나 멀티 스레드도 역시 데드락이라는 문제점을 가지고 있다.
스레드
- 화면 UI의 리소스 객체는 메인 스레드에서 관리하기 때문에 서브 스레드에서 접근할 수 없다.
- 안드로이드에서 메인 스레드에서만 UI 작업이 가능한 이유는, 두 개 이상의 스레드가 동시에 화면에 접근하는 것을 막기 위함이다.
동기와 비동기
동기
- 동기적이란 요청을 보낸 후 응답(결과)를 받아야 다음 동작을 수행하는 방식이다.
- 프로그래밍에서 순서대로 작업을 수행하여 1개의 동작을 완료한 후 다른 동작을 실행하는 방식이다.
- 다양한 기능이 한꺼번에 일어나는 다중 실행 환경에서는 성능상 제약이 발생한다.
- 다중 실행 환경이란 UI 갱신, 네트워크 작업 등 다양한 작업을 동시에 대응해야 하는 실행 환경을 말한다.
비동기
- 비동기란 요청을 보낸 후 응답(결과)과는 상관없이 다음 동작을 수행하는 방식이다.
- 여러 개의 동작이 선행 작업의 순서나 완료 여부와 상관없이 함께 실행되는 방식이다.
- 안드로이드에서는 AsyncTask, RxJava, Coroutine을 사용하여 비동기 방식을 처리한다.
- 코틀린에서는 코루틴(Coroutine)을 사용하여 비동기 작업을 처리한다.
Coroutine 코루틴
Thread
- 선점형 멀티태스킹
- 작업 단위는 쓰레드로, 작업 각각에 쓰레드를 할당한다. 쓰레드는 자체 Stack 메모리 영역을 확보한다.
- 쓰레드간 전환 시에 운영체제 거널에 의한 문맥 교환이 필요하므로 그만큼의 비용이 발생한다.
- 멀티 쓰레드에서 병행성과 병렬성을 달성한다.
- 쓰레드와 콜백을 많이 사용하면 코드가 복잡해진다.
Coroutine
- 비선점형 멀티태스킹
- 작업 단위는 Coroutine object로, 작업 각각에 Coroutine object를 할당하고, 이때 Coroutine object는 Heap 영역에 적재된다.
- 쓰레드위에서 동작하는 light-weight 쓰레드로 병행성(동시성)을 제공 (병렬성은 제공하지 않는다)하기 때문에 여러 작업이 동시성을 구현하면서도 스레드보다 비용이 적은 멀티태스킹 방식이다.
- 코루틴은 문맥 교환없이 해당 루틴을 일시 중단을 통해 제어한다.
- 코루틴은 비동기 코드를 마치 동기 코드처럼 쉽게 작성하면서도 비동기 효과를 낼 수 있다.
- 하나의 개별적인 작업을 루틴이라고 하는데, 코루틴이란 여러 개의 루틴들이 협력한다는 의미의 합성어다.
- 코루틴은 메인루틴과 별도로 코루틴을 생성하여 루틴의 실행과 종료를 제어할 수 있는 비동기 처리방식이다.
코루틴 구조
- Coroutine Scope: 코루틴 동작 범위 지정
- Coroutine Context: 코루틴 실행 스레드 지정
- Coroutine Builder: 코루틴 실행 빌더 (작업 시작)
Coroutine Scope
코루틴이 동작하는 범위를 지정하는 것으로 CoroutineScope, GlobalScope로 구분한다.
- CoroutineScope
- CoroutineContext를 가지고 있는 인터페이스로서 동작 범위(Scope)가 결정되면, 특정한 Dispatcher를 지정해서 스코프가 수행될 스레드를 지정한다.
- 필요할 때 매번 생성하고, 사용을 마치면 종료한다.
- GlobalScope
- 애플리케이션의 생명주기를 갖는 singleton object로서, 애플리케이션 레벨(Top-level)에서 코루틴을 실행한다.
- 따라서 GlobalScope.launch() 로 실행한 코루틴은 애플리케이션이 종료되지 않는 한 필요한 만큼 실행한다.
Coroutine Context
- CoroutineContext는 어떤 스레드에서 코루틴을 실행할 것인지 결정하는 역할을 맡는다.
- 어떤 스레드에서 해당 코루틴을 실행할지에 대한 Dispatchers 정보를 담고 있다.
- Dispatchers는 코루틴의 실행을 진행할 특정 스레드를 지정하거나, 스레드 pool을 지정한다.
(코루틴에서 사용할 스케줄러를 정의 - Default(Work Thread), IO, Main(UI Thread) 등 ) - 안드로이드에서 I/O 작업을 백그라운드 스레드에서 수행하고, 화면 갱신을 UI 스레드에서 처리하는 것처럼, 실행에 따라 사용할 스레드를 지정해야 하는데, 이때 CoroutineContext를 사용한다.
CoroutineBuilder
코루틴은 빌더를 사용하여 코루틴을 생성하고, 실행코드를 CoroutineScope 안에서 실행한다.
1.launch
- 일반적인 코루틴을 시작할 때 사용하며, launch에서 사용할 스레드를 지정하지 않으면, CoroutineScope의 스케줄러를 따르며, 지정할 경우 지정된 스레드에서 동작한다.
- 기본적으로 즉시 실행하며 블록 내의 실행 결과를 반환하지 않는다.
- launch는 CoroutineScope를 실행하고 job 객체를 리턴한다.
- job을 이용하여 코루틴 실행을 모니터링 및 제어할 수 있다.
2. async
- 기본적으로 launch와 같은 동작을 하지만 실행 결과를 반환한다.
- 실행 결과는 Deffered<T> 타입으로 객체를 반환하며, await() 함수를 통해 결과를 받는다.
- await은 코루틴의 작업이 완료될 때까지 대기한다.
3.runBlocking
- runBlocking은 scope 빌더로, runBlocking 블록을 실행하는 동안 main 스레드가 blocking 된다.
4. withContext
- Dispatchers를 전환하는 기능을 수행하는 scope 빌더다.
- 특정 스레드에서 코루틴을 수행하다, 필요에 따라 특정 스레드로 전환하여 어떤 작업을 수행해야 할 때 사용한다.
- 예를 들어 IO 스레드에서 네트워킹 작업을 수행하다, 응답 결과를 UI에 갱신하기 위해 main 스레드로 전환한다.
Job
- Job은 interface로 정의되어 있으며 현재 실행되고 있는 코루틴의 상태 값을 가지고 있는 인스턴스 객체다.
- CoroutineScope launch{} 사용 시 job 객체를 반환한다.
- Job을 통해 routine의 취소, 실행, 종료, 대기를 할 수 있다.
- job.start(): coroutine의 동작 상태를 확인한다. (true: 동작 중, false: 준비 중/ 완료)
- job.join(): coroutineScope의 동작이 종료할 때까지 대기
- job.cancel(): coroutineScope의 동작 종료를 유도한다.
- job.cancelAndJoin(): coroutineScope의 동작 종료를 요청하고 대기한다.
- 자식 코루틴은 부모 코루틴의 job이 취소되면 함께 취소되며, 자식 코루틴만 취소하고 싶다면 별도의 job을 할당하여 취소한다.
job을 사용하는 방법은 2가지가 있다.
- 특정 CoroutineScope에서 launch의 반환 값을 사용하여 제어하는 방법이 있다.
- 전역에서 여러 개의 코루틴을 한 번에 제어하려면 독립적인 job을 생성하고, 이를 특정 CoroutineScope를 선언할 때,
CoroutineContext + job 형태로 선언한다.
코루틴 지연 메서드는 3가지가 있다.
- delay
- join
- await
코루틴 취소 메서드는 4가지가 있다.
- cancel
- cancelAndJoin
- withTimeout
- withTimeoutOrNull
예제 코드
우선 프로젝트에 코루틴 의존성을 추가해야한다. build.gradle에 다음과 같은 코드를 추가하자.
implementation 'org.jetbrains.kotlin:kotlinx-coroutines-core:1.6.0-native-mt'
먼저 CoroutineEx01.kt 코드다.
fun main(){
println("Current Thread = ${Thread.currentThread().name}")
GlobalScope.launch {
println("Current Thread = ${Thread.currentThread().name}")
delay(1000)
println("World!!!")
}
println("Hello")
Thread.sleep(2000)
}
출력 결과는 아래와 같다.
다음은 CoroutineEx02.kt 코드다.
fun main(){
println("Current Thread = ${Thread.currentThread().name}")
GlobalScope.launch {
println("Current Thread = ${Thread.currentThread().name}")
delay(1000)
println("World!!!")
}
println("Hello")
//새로운 코루틴을 생성하고, 실행이 완료될 때까지 현재 스레드를 블라킹한다.
runBlocking {
delay(1000)
}
}
이상으로 포스팅을 마칩니다. 감사합니다.
참고자료
https://aaronryu.github.io/2019/05/27/coroutine-and-thread/
https://developer.android.com/kotlin/coroutines?hl=ko
반응형
댓글