본문 바로가기
Programming/Languages (Java, etc)

[Kotlin] runBlocking vs coroutineScope

by kghworks 2024. 8. 2.

kotlin

 

 코루틴은 Structured Concurrency 원칙을 따른다. Structured Concurrency이란 새로운 코루틴은 반드시 CoroutineScope 안에서만 생성 (런치)될 수 있다는 것으로 코루틴의 범위를 제한하는 메커니즘이다. 코루틴의 범위를 제한하고 코루틴의 라이프사이클을 관리하는 인터페이스는 kotlinx.coroutines.CoroutineScope이다. 

 

Scope builder

 Scope builder란 코루틴의 범위를 나타내는 인터페이스 CorotuineScope을 생성하고, 그 안에서 생성된 모든 코루틴 (자식 코루틴)이 완료될 때까지 기다리는 빌더 함수 (람다)를 말한다. 코루틴 스코프의 최상위 코루틴 (부모 코루틴)은 블로킹된다. 스코프 빌더는 크게 2가지 runBlocking {}과 coroutineScope {}이 있다.


runBlocking {} vs coroutineScope {}

 두 빌더는 공통적으로 해당 스코프의 모든 코루틴이 종료될 때까지 최상위 코루틴이 블로킹된다는 점이 있다. 하지만 runBlocking {}은 현재 스레드를 블로킹하지만 coroutineScope {}susped funciton으로서 현재 스레드를 릴리즈한다. 아래 두 코드가 어떻게 실행되는지 확인해 보자. 

// runBlocking
fun main() = runBlocking {
        launch {
            delay(1000L)
            println("World!")
        }

        println("Hello,")
    }

// coroutineScope
fun main() = runBlocking {
    coroutineScope {
        launch {
            delay(1000L)
            println("World!")
        }
    }
    println("Hello,")
}

 

 runBlocking {}을 활용한 것은 Hello, World! 로 콘솔에 찍히지만 coroutineScope {}을 사용한 밑에 main()은 World! Hello, 로 콘솔에 찍힌다. 아래 main() 함수는 아래 순서로 실행되기 때문이다.

 

  1. main() 실행, runBlocking 빌더로 코루틴 A 생성 (main 스레드 blocking)
  2. coroutineScope 빌더로 코루틴 스코프 생성 (여기서 코루틴 A는 중지되어 해당 스코프가 종료될 때까지 기다림)
  3. 해당 스코프에서 코루틴 B 생성
  4. 코루틴 B 1초 동안 중지 후 World! 출력
  5. 코루틴 B 종료, 코루틴 스코프 종료
  6. 코루틴 A Hello, 출력 후 종료, main() 종료

 

따라서 해당 main()함수가 Hello, World!로 콘솔에 나오려면 아래처럼 수정해야 한다.

fun main() = runBlocking {
    coroutineScope {
        launch {
            delay(1000L)
            println("World!")
        }
        println("Hello,")
    }
}

 

차이점 1. 빌더와 동시에 코루틴 생성 여부

runBlocking{}에서 break point

 

coroutineScope{}에서 break point

 

 IntelliJ Debug 탭을 사용하면 실시간 코루틴 정보를 얻을 수 있다. 위 캡처화면은 코드의 break point 부분에서 확인한 현재 코루틴의 상황이다. 보시다시피 runBlocking은 body 실행과 동시에 새로운 해당 스코프의 자식 코루틴을 만들어 실행한다. 이는 해당 빌더 docs에서도 확인이 가능하다. 그러나 coroutineScope {} 빌더는 main 스레드를 실행하던 코루틴에서 이어서 계속 실행되는 것을 볼 수 있다. 

runBlocking docs

 

coroutineScope docs

차이점 2. 스레드 블로킹 여부

 runBlocking {}을 실행한 스레드는 blocking 된다. 즉 생성된 코루틴이 스레드를 릴리스해주지 않는다. 난 이걸 바인딩된다고 표현한다. 반면 coroutineScope {} 은 단지 suspend funciton으로 정의된 body block을 실행할 뿐이다. 아래 두 메서드의 실행을 비교해 보자

demoWithCoroutineScope{} 결과

  1. demoWithCoroutineScope을 스레드 A, 코루틴 a로 실행 blocking. A-a binding
  2. 스레드 A, 코루틴 a에서 자식 코루틴 a-1 생성 후 coroutineScope 진입
    1. coroutineScope에서 a가 delay (suspend), 스레드 A를 release
  3. 스레드 B, 코루틴 a의 자식 코루틴 a-2 생성 후 coroutineScope 진입
    1. coroutineScope에서 a-2가 delay (suspend), 스레드 B를 release
  4. 스레드 B, 코루틴 a의 자식 코루틴 a-3 생성 후 coroutineScope 진입
    1. coroutineScope에서 a-3이 delay (suspend), 스레드 B를 release.... (반복)
  5. 2에서 중지된 코루틴 a-1, 스레드 B에서 재개... (반복)

demoWithRunBlocking{} 결과

  1. demoWithCoroutineScope을 스레드 A, 코루틴 a로 실행 blocking. A-a binding
  2. 스레드 A, 코루틴 a에서 자식 코루틴 a-1 생성 후 runBlocking에 진입 (이때, 코루틴 a-1이 스레드 a에 바인딩되어 blocking) => 즉 자식 코루틴 a-1이 완료될 때까지 스레드 A를 release 하지 않는다.
  3. 스레드 B, 코루틴 a의 자식 코루틴 a-2 생성 후 runBlocking에 진입 (이때, 코루틴 a-2가 스레드 B에 바인딩되어 blocking) => 즉 자식 코루틴 a-2가 완료될 때까지 스레드 B를 release 하지 않는다.
  4. 스레드가 총 2개라 다음 코루틴을 론칭할 수 없음
  5. 2에서 중지된 코루틴 a-1, 스레드 A에서 재개, 완료 후 스레드 A release
  6. 3에서 중지된 코루틴 a-2, 스레드 B에서 재개, 완료 후 스레드 B release
  7. ... (반복)

runBlocking {}은 언제 쓰는가

https://kotlinlang.org/docs/coroutines-basics.html#structured-concurrency

그럼 runBlocking {}은 언제 쓰는가? 공식 문서에도 나와있든 runBlocking {}은 application 내부에서 극히 드물게 쓰이지만, 스레드 세상과 코루틴 세상을 최초 연결할 때는 반드시 필요한 빌더이다. 따라서 application 최초 시작 지점과 같은 곳에서 드물지만 피수적으로 쓰이게 된다.


참고

https://kotlinlang.org/docs/coroutines-basics.html

 

Coroutines basics | Kotlin

 

kotlinlang.org

 

https://www.baeldung.com/kotlin/coroutines-runblocking-coroutinescope

 

댓글