plzy의 개발 블로그

[Coroutine] 기초를 알아보자. (1) 본문

Android

[Coroutine] 기초를 알아보자. (1)

plzyhappy 2022. 7. 7. 20:45

이 블로그 포스팅은 https://myungpyo.medium.com/reading-coroutine-official-guide-thoroughly-part-1-98f6e792bd5b 를 참고하여 만들었습니다.

#1 중단 함수에 대해 알아보자.

package com.smp.coroutinesample.basic

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch


fun main(args: Array<String>) {
    GlobalScope.launch {
        delay(1000L)
        println("World!")
    }
    println("Hello,")
    Thread.sleep(2000L)
}

위의 코드를 실행해보면 어떤 결과가 나올까?

출력 결과

Hello World!가 출력되는 걸 확인할 수 있다. 만약 마지막 줄인 Thread.sleep을 지워보자.
지웠더니 Hello, 만 출력되고 World! 는 출력되지 않는다. 이유가 무엇일까?
코루틴은 자신이 실행한 호출 스레드를 블록 하지 않기 않기 때문에 메인 함수가 종료되면 메인 함수를 호출한 메인 스레드 역시 종료된다.
이를 방지하기 위해 마지막 줄인 Thread.Sleep 함수로 임의의 시간을 지정하여 스레드를 지연시켜줬다.
이런 스레드를 멈추는 역할을 수행하는 함수를 중단 함수 라고 한다.

중단 함수 사용하는 방법

fun main(args: Array<String>) {
    GlobalScope.launch {
        delay(1000L)
        println("World!")
    }
    println("Hello,")
    runBlocking {
        delay(2000L)
    }
}

중단 함수를 사용하려면 코루틴 빌더 안에 있어야 한다. 이를 명시적으로 나타내 주기 위해 runBlocking(코루틴 빌더)를 사용했다.
runBlocking은 사용하기 권장하지 않는다.
왜냐하면 코루틴이 완료될 때 까지 메인 스레드를 block 하는데코루틴이 완료될 때까지의 시간이 얼마나 걸릴지 모른다. 이때 시간이 너무 오래 걸릴 경우 ANR이 발생할 가능성이 생긴다.

위의 코드에서 delay 중단 함수로 지연시켰다. 하지만 이건 좋은 방법이 아니다.
자식 코루틴이 언제 끝날지 부모 코루틴은 알 수 없다.

fun main(args: Array<String>) = runBlocking {
    val job = GlobalScope.launch {
        delay(1000L)
        println("World!")
    }
    println("Hello,")
    job.join()
}

이를 해결하기 위해 GlobalScope.launch 반환값인 Job을 사용한다.
이 job 객체로 코루틴 빌더로 생성된 코루틴을 종료될 때까지 대기할 수 있다.

만약 메인 코루틴에 두 개의 자식 코루틴이 있다고 해보자.
메인 코루틴이 종료될 때 두 개의 자식 코루틴이 실행될 때까지 기다렸다가 종료시켜야 한다.
이러한 과정이 번거로워 scope를 사용한다.

fun main(args: Array<String>) = runBlocking {
    launch {
        delay(1000L)
        println("World!")
    }
    println("Hello,")
}

위 코드의 launch를 살펴보자. launch 코루틴 빌더로 코루틴을 생성하면
Job join 할 필요 없이 자식 코루틴을 실행하고 종료될 때까지 기다릴 수 있다.

Scope Builder를 사용해보자.

위의 예제들을 보면 Scope Builder로 runBlocking을 사용했다.
scope builder은 이것 이외에도 여러 가지가 있다.
이중 coroutineScope를 사용할 수 있는데 runBlocking과 비교했을 때 coroutineScope는 자식 코루틴을 대기하는 동안 현제 스래드를 블록 하지 않는다.

fun main(args: Array<String>) = runBlocking {
    launch {
        delay(200L)
        println("Task from runBlocking")
    }

    coroutineScope {
        launch {
            delay(500L)
            println("Task from nested launch")
        }
        delay(100L)
        println("Task from coroutine scope")
    }
    println("Coroutine scope is over")
}

위의 코드의 실행결과로

Task from coroutine scope
Task from runBlocking
Task from nested launch
Coroutine scope is over 순서대로 출력된다.

Suspend에 대해 알기

위의 예제들은 메인 함수 안에 모든 로직을 구상했지만, 우리는 각각의 함수로 나뉘어 사용하는 경우가 흔하다.

fun main(args: Array<String>) = runBlocking {
    launch {
        doWorld()
    }
    println("Hello,")
}

suspend fun doWorld() {
    delay(1000L)
    println("World!")
}

위의 코드와 같이 suspend를 붙인 함수는 코루틴 빌더와 같이 중단 함수를 사용할 수 있다.

경량 스레드

일반 스레드로 메모리 부족 걸리는 작업일지라도 코루틴을 사용하면 정상적으로 작동한다.

데몬 스레드

fun main(args: Array<String>) = runBlocking {
    GlobalScope.launch {
        repeat(1000) { i ->
            println("I'm sleeping $i ...")
            delay(500L)
        }
    }
    delay(1300L)
}

코드만 봤을 때는 500ms 로 1000번 출력될 것 같지만 메인함수는 이보다 짧은 시간 대기후 종료된다.

위의 예제와 같이 GlobalScope는 데몬 스레드와 같이 프로세스 종료를 지연 안 시키고 프로세스 종료 시 코루틴도 종료시켜
정해진 시간에만 동작한다.

참고한 레퍼런스

https://myungpyo.medium.com/reading-coroutine-official-guide-thoroughly-part-1-98f6e792bd5b

 

코루틴 공식 가이드 읽고 분석하기 — Part 1

공식 가이드 읽기 ( 1/8 )

myungpyo.medium.com

팀 블로그에 쓴 글

https://medium.com/sseukssak/coroutine%EC%97%90-%EB%8C%80%ED%95%B4-%EC%95%8C%EC%95%84%EB%B3%B4%EC%9E%90-1-ccf3162a141e

 

Coroutine에 대해 알아보자! (1)

Coroutine에 대해 알아보자!

medium.com