Android Kotlin : Coroutine과 Thread

2022. 9. 15. 01:54[Android APP] feat. Kotlin/Kotlin 공부

개요

Coroutine과 Thread를 알기 전에 이들은 어디에 사용되는 것일까?

먼저 비동기 처리를 알아야한다.

 

비동기(Asynchronous : 동시에 일어나지 않는)란?

특정 로직의 실행이 끝날때까지 기다려주지 않고 나머지 코드를 먼저 실행하는 것을 뜻한다.

쉽게 말하면 동시에 여러 작업을 할 수 있도록 도와주는 것이다.

 

Coroutine과 Thread는 이같은 비동기 작업에 사용한다.

안드로이드에서 네트워크의 값을 가져올 때 Main Thread가 아닌 별도의 Thread에서 동작하도록 구현해야한다.

 

 

본문

Thread가 뭐고 코루틴이 뭐야?

Thread 

1. 각 태스크에 해당하는 스택 메모리를 할당받는다.

(각 Thread가 독립적인 Stack 메모리 영역을 가진다)

 

2. 여러 작업을 동시에 수행해야할 때 OS 는 어떤 쓰레드 작업을 먼저 수행할지, 어떤 쓰레드를 더 많이 수행해야 효율적인지에 대한 스케쥴링 (선점 스케쥴링, Preempting Scheduling) 을 한다.

 

3. 운영체제 커널에 의한 Context Switching 을 통해 동시성 보장

블로킹 (Blocking) : Thread1이 Thread 2의 결과를 기다려야한다면 Thread 1 은 블로킹되고 Thread 2 의 결과가 나올 때 까지 해당 자원을 쓰지 못한다.

 

Coroutine 

1. Lightweight Thread 라고 부른다.

 

2. 작업 하나하나에 Thread 를 할당하는 것이 아닌 'Object' 를 할당해주고, 이 Object 를 자유롭게 스위칭함으로써 Context Switching 비용을 대폭 줄인다.

 

3. 코드를 통해 Switching 시점을 정한다.

Suspend (Non-Blocking) : Object 1 이 Object 2 의 결과가 나오기까지 기다려야 한다면, Object 1 은 Suspend 되지만, Object 1 을 수행하던 Thread 는 그대로 유효하기 때문에 Object 2 도 Object 1 과 동일한 Thread 에서 실행될 수 있다.

 

 

Coroutine 은 Thread 의 대안이 아니라, Thread 를 더 잘게 쪼개어 사용하기 위한 개념이다.

  • 작업의 단위를 Object 로 축소하면서 하나의 Thread 가 다수의 코루틴을 다룰 수 있기 때문에, 작업 하나하나에 Thread 를 할당하며 메모리 낭비, Context Switching 비용 낭비를 할 필요가 없다.

 

코루틴의 종류

1) 코루틴 스코프 ( CoroutineScope)

  • 글로벌 스코프(GlobalScope): 앱의 생명주기와 함께 동작하므로 생명주기를 따로 관리해 줄 필요가 없다.
  • 앱이 시작할 때 부터 끝날 때 까지 긴 시간 실행되는 코루틴의 적합하다.(메인 스레드가 닫히면 같이 닫힌다.)
  • 코루틴 스코프(CoroutineScope):  코루틴이 필요할 때만 사용하고 닫아주는 경우에 적합하다.

2) 코루틴 디스패쳐 ( Dispatcher )

디스패처는 코루틴을 적당한 스레드에 할당하고 코루틴이 정지하거나 다시 실행하는 것을 담당한다.
코루틴 디스패처의 종류에는 Default, IO, Main, Unconfined 등이 있다.

  • Dispatchers.Default : 안드로이드 기본 스레드 풀을 사용한다. CPU를 많이 쓰는 데이터 정렬이나 복잡한 연산 같은 작업에 최적화 되어있다.
  • Dispatchers.IO : 이미지 다운로드, 파일 입출력 등 네트워크, 디스크, DB작업에 최적화 되어있다.
  • Dispatchers.Main : 안드로이드 기본 스레드에서 코루틴을 실행한다. UI와 상호작용에 최적화 되어있다.
  • Dispatchers.Unconfined : 호출한 context 를 기본으로 사용하는데 중단하고 다시 실행될 때 context가 바뀌면 바뀐 context로 따라간다.

3) 코루틴 상태관리

  • cancel : 코루틴의 동작을 멈추게 하는 method이다. 하나의 스포크 안에 여러개의 코루틴이 존재할 때 하위 코루틴까지 모두 멈춘다.
  • join : 코루틴 내부에 여러 launch 블록이 있을 경우 모두 새로운 코루틴으로 분기되어 동시에 실행되기 때문에 순서를 정할 수 없다. 순서를 정해야할때 join method 를 이용해서 코루틴이 순차적으로 실행할 수 있도록 할 수 있다.

4) 코루틴 suspend 함수

suspend가 붙은 함수가 호출되면 호출한 코드를 잠시 멈추게 해서 스레드가 멈추지 않는 효율적인 스레드 활용을 할 수 있게 된다. 코루틴이 멈출 때 코틀린 런타임은 해당 코루틴이 실행되던 스레드에 다른 코루틴을 할당해서 실행시킨다. 그리고 멈춰있던 코루틴이 다시 실행될 때 사용 가능한 스레드에 할당해준다. 코루틴은 멈추면서 해당 루틴 상태를 저장하고 서브 루틴을 실행한 다음 저장한 부모루틴을 복원하는 방법으로 스레드에 영향을 주지 않는다.

 

5) 코루틴 빌더

  • launch : 현재 스레드를 중단하지 않고 코루틴을 바로 시작한다. 결과를 호출한 쪽에 반환하지 않는다. 일반 함수에서 suspend 함수를 호출할 때와 코루틴의 결과가 필요 없을 때 사용한다.
  • async : 현재 스레드 중단 없이 코루틴을 바로 시작한다. launch와 다른점은 호출 쪽에서 await() 을 통해 코루틴 결과를 기다릴 수 있다. 다수의 코루틴을 사용할 때 사용한다. suspend 함수 내부에서만 사용할 수 있다.
  • withContext : 부모 코루틴에 의해 사용되던 context와 다른 context에서 코루틴을 실행 할 수 있다.
  • coroutineScope : 다수의 코루틴을 suspend 함수가 시작하고 모든 코루틴이 완료될 때만 어떤 처리가 필요한 경우에 적합하다. 여러 코루틴 중에서 하나라도 실패하면 모든 코루틴이 취소된다.
  • supervisorScope : coroutineScope와 비슷하지만 여러 코루틴 중에서 하나가 실패해도 다르 코루틴이 취소되지 않는 다는 점이 다르다.
  • runBlocking : 코루틴을 시작하고 완료될 때 까지 현재 스레드를 중단시킨다.

5-1) 코루틴 suspend function

  • withTimeout : 코루틴이 정해진 시간 안에 실행되지 않으면 예외를 발생시킨다.
  • withTimeoutOrNull : 정해진 시간 안에 실행되지 않으면 null을 반환한다.
  • awaitAll : 모든 작업의 성공을 기다리면서 작업중에 하나라도 실패하면 awaitAll 또한 실패한다.
  • joinAll : 모든 작업이 끝날 때 까지 현재 작업을 일시 중단시킨다.

 

 

코루틴 사용법

app gradle에 아래 줄을 추가해준다.

    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0'

 

코루틴의 Job(작업) 추가

private var job : Job?= null
job = GlobalScope.launch(Dispatchers.IO) {

   //작업...  
   
   delay(1000) 
}

 

코루틴 실행

job.join()

 

코루틴 종료

job.cancel()

 

Thread 사용법

Thread{
    
    //작업
    
}.start()
반응형