계발자 블로그

Coroutine 본문

Kotlin

Coroutine

더구더구 2022. 9. 27. 00:17

안드로이드에서는 기존 AsyncTask를 이용해서 비동기 프로그래밍을 구현했습니다.

하지만 AsyncTask에는 메모리 누수등 여러가지 메모리 관련한 문제가 있어서

API 30부터 deprecated 되었습니다.

그 대체재로 코루틴을 권장하고 있습니다.

코루틴이 코틀린과 이름이 비슷하지만 코틀린만의 고유한 개념은 아닙니다.

 

우선 프로그램에는 루틴이 있습니다.

프로그램의 흐름을 일컫는 추상적인 개념입니다.

  • 메인루틴
    • 메인 함수에 의해서 진행 되는 프로그램의 흐름
  • 서브루틴
    • 메인함수 안에서 수행 되는 개별 함수들의 흐름

보통 루틴은 일직선 적인 흐름입니다.

하지만 코루틴의 특징은 일직선 적인 루틴의 특징을 suspend로 지연을 시키고 resume으로 재시작이 가능합니다.

 

구글에서 코루틴을 AsyncTask를 대체해서 쓰라하기 떄문에

코루틴을 AsyncTask의 약점인 메모리 누수를 고친 스레드라고 생각하기 쉽습니다.

하지만 코루틴은 스레드가 아닙니다.

 

코루틴과 스레드 차이

  • 메모리 구조의 차이
    • 스레드는 스택이라는 독립된 메모리 영역을 할당 받음
    • 코루틴은 스택을 할당 받지 않고 프로세스에 할당된 힙 메모리 영역을 공유해서 사용하게 됌.
    • 코루틴은 스레드 보다 함수의 가까운 구조

 

  • 수행방식의 차이
    • 코루틴은 비선점형 멀티태스킹
    • 스레드는 선점형 멀티태스킹
    • 스레드는 멀티코어를 사용을 해서 동일한 시간에 두가지 작업을 할 수 있음 (병행성이 있다)
    • 코루틴은 동일한 시간에 동시에 수행되지 않음 다만 코루틴이 전환 되면서 수행되는 속도가 매우 빠르기 때문에 외부에서 볼때는 코루틴 두개가 동시에 수행되는 것처럼 보임(동시성은 있지만, 실제로 동시에 처리하는 것이 아니라 병행성은 없다)

 

코루틴의 장점

세개의 스레드를 사용해야 되는 작업이 있다면 이것을 코루틴으로 대체한다면

스레드 마다 할당해야 했던 스택 메모리를 코루틴에서는 할당 할 필요가 없습니다.

그렇다면 당연히 사용되는 메모리가 줄어들게 됩니다.

 

스레드를 사용하게 되면 분리된 스택영역이 세개가 생기게 되는데

a스레드를 처리하다 b스레드를 처리해야 될 때 스레드 사이에서 context를 바꿔야 할 경우가 있습니다

이 때 서로 다른 메모리 영역에서 참조를 하기 위해 옮겨가야 되는 작업을 해야 되는데

이것을 context switching이라고 합니다.

 

코루틴에서는 분리된 메모리를 사용하는 것이 아니라 프로세스가 가진 힙 메모리 안에서

수행 되기 때문에 context switching 자체가 필요하지 않습니다. 그러므로 오버헤드가 줄어들게 됩니다.

 

또한 스레드안에서 여러개의 코루틴을 돌아가게 할수 있어서 스레드를 불필요하게 만들 필요가 없습니다.

 

코루틴 사용법

함수 앞에 suspend라는 접두어를 붙이게 되면 이 함수는 중간에 멈출 수 있는 함수가 됩니다.

 

코루틴 구조

  • Coroutine Scope
  • Coroutine Context
  • Coroutine Builder

 

가장 큰 Coroutine Scope가 정해지면

그 안에서 어떤 스레드에서 코루틴을 실행 할지 결정하는 Coroutine Context가 정해집니다.

그 다음 이 작업을 수행을 하고 값을 리턴을 할지 안할지 정해주는 Coroutine Builder를 실행하게 되고

코루틴 객체가 생기면서 코루틴 작업을 수행할수 있게 됩니다.

 

Coroutine Scope

코루틴이 동작하는 범위를 규정하는 부분

 

public interface CoroutineScope {
	public val coroutineContext: CoroutineContext
}

Coroutine Scope는 이런 형태로 결정되는 인터페이스입니다.

Scope가 결정이 되면 이 안에서 특정한 dispatcher를 지정해서 동작이 실행 될 Scope의 영역을 제안할 수 있습니다.

 

Coroutine Context

코루틴은  Coroutine Context 안에서 실행이 됩니다.

dispatcher와 job으로 구성됩니다.

 

Dispatcher

dispatcher는 코루틴이 실행이 되는 스레드를 지정하게됩니다

4가지 타입이 있습니다

용도마다 사용하는 디스패처가 다릅니다

  • Default
    • cpu 연산을 많이 하는 작업
  • IO
    • 파일 io, 네트워크 io
    • Main ui 스레드(메인 스레드) ui 관련된 변경 할 때 사용
  • Unconfined
    • 일반적인 용도로는 사용하지 않음

 

Job

코틀린에서는 코루틴 작업을 잡 혹은 deferred라는 오브젝트로 만들어서 다루게 됩니다.

deferred는 결과값을 가지는 job이기 때문에 사실상 job이라고 생각하면 됩니다.

코루틴이라는 추상적인 흐름을 job이라는 오브젝트로 만들어서 사용을 합니다

코루틴 한 덩어리를 job이라는 오브젝트로 만들면 취소나 예외처리를 함으로써

코루틴 흐름 제어를 용이하게 할수 있습니다.

val job = scope.launch {
	//New coroutine
}

 

 

위 코드 처럼 코루틴 스코프에 대해서 launch 명령으로 코루틴 작업을 만들면

그 작업이 job이라는 객체를 반환하게 됩니다.

그 job에 대해서 cancel 명령으로 취소를 함으로써 이처럼 흐름제어를 할수 있습니다.

 

job에는 메소드가 있습니다

  • start
    • 새로 만들어진 객채가 시작됩니다.
  • cancel
    • 작업중인 코루틴을 canceling 상태로 바꿀수있습니다.
  • join
    • cancel 된 작업을 병렬 처리 하지 않고 완전히 끝날때까지 기다리게 하는 메소드입니다.

 

Coroutine Builder

context까지 정해졌으면 builder를 사용해서 코루틴을 만들면 됩니다.

  • launch
    • 일반적으로 런치를 사용하여 코루틴을 시작하면 됩니다 앞에서 설명 잡 객체가 반환이 됩니다 
    • 코루틴 작업이 실행 되면 그걸로 끝입니다.
  • async
    • launch 대신 async를사용할수 있습니다 deferred객체가 반환이 됩니다.
    • 작업이 실행 되면 마지막에 나온 값을 반환하게 됩니다 반환 되는 값은 defferd 형태의 오브젝트로 반환이 됩니다.

런치랑 어싱크는 메인스레드와 관계 없는 다른 스레드에서 돌아가기 떄문에 메인스레드를 방해하지 않습니다

 

  • runBlocking
    • 메인스레드를 블럭을 한다음에 작업을 실행하게 됩니다. 그동안 메인스레드가 멈추게 됩니다.
    • 테스트 용도 말고는 실제로 코루틴을 사용하기 위해서는 사용하지 않습니다.
  • withContext
    • dispatcher를 전환시키는 dispatcher switch는 기능을 갖고있습니다 
    • 안드로이드 os에서 스위치를 관리를 하기 때문에 오버헤드를 훨씬 적게 관리 할 수 있습니다.

 

코루틴 지연

delay

정해진 시간 동안 중단이 됩니다 스레드의 슬립과 비슷합니다.

스레드 슬립은 그 자체를 멈추지만 딜레이는 코루틴이 멈추는게 아니고 숫자를 세면서 대기를 하는 상태입니다.

join

launch로 실행한 코루틴에서는 join으로 job의 실행이 끝날떄까지 대기 시킬수 있습니다

await

async로 실행한 코루틴에서는 await로 deferred의 실행이 끝날때까지 대시 시킬수 있습니다.

 

코루틴 취소

cancel

job을 canceling 상태로 바꿀 수 있습니다

cancelAndJoin

withTimeout

어떤 처리를 할 때 제한 시간 내에 수행하지 못하면 취소를 하고 예외처리(Exception)를 하는 메소드입니다.

withTimeoutOrNull

withTimeout과 같지만 exception 대신 null을 받아 옵니다.

 

예외처리

CoroutineExceptionHandler를 이용하여 try catch 블럭을 만들어 exception 처리를 할 수 있습니다.

 

이때 launch와 async에서 차이가 있습니다.

 

launch는 그자리에서 바로 예외가 발생합니다.

async는 중간에 exception이 발생을 해도 하던 작업은 계속 수행을 하고 await를 만나야 예외처리를 하게 됩니다.

 

job.cacel()을 제외안 다른 문제로 인한 exception이 발생을 하면 부모의 코루틴까지 모두 취소가 됩니다.

코틀린에서 설계된 structured concurrency를 유지하기 위함으로

CoroutineExceptionHandler 사용해도 막을 수가 없습니다

무조건 자식 코루틴에서 exception이 발생을 하면 부모 코루틴까지 취소가 됩니다.

 

정리

코루틴은 스레드가 아니고 스레드와 비교하면

메모리를 덜 쓰고 오버헤드가 적은 경량의 비동기 프로그래밍을 수행 할 수 있게 하는 모듈이라고 할수 있습니다

 

코틀린에서 코루틴을 사용하기 위해서는 CoroutineScope, Coroutine Context, Coroutine Builder를 사용하여 코루틴 객체를 만들어서 사용하면 됩니다.

이때 Scope는  CoroutineScope를 사용합니다.

cpu 연산 작업이 많으면 dispatcher default를 사용하면 되고

파일이나 네트워크 IO 작업이 많으면 IO dispatcher 사용하면 됩니다.

 

코루틴을 처리 할 때 코루틴만 처리하면 되는 작업이라면 launch로 빌드하고

처리를 한 다음에 어떤 값을 반환해야 한다면 async로 빌드를 하면 됩니다.

 

 

 

 

 

 

출처 : 인프런 냉동코더의 알기 쉬운 Modern Android Development 입문 강의

 

'Kotlin' 카테고리의 다른 글

[Kotlin] 코틀린 Companion Object  (0) 2023.03.01
[Kotlin] Kotlin Property (코틀린 프로퍼티)  (0) 2023.03.01
[Kotlin] Scope Function  (0) 2022.03.30
[Kotlin] Collection  (0) 2021.12.10
[Coroutine] LifecycleScope  (0) 2021.10.28