본문 바로가기
자바와 코틀린

코루틴 - 중단함수

by pius712 2024. 9. 17.

중단함수

중단함수란?

중단 함수란 코루틴을 중단할 수 있는 중단점을 제공하는 함수를 말한다.

suspend fun 을 통해서 구현할 수 있다.

기본적으로 일반 함수와 크게 다르지 않으나, 내부에 중단지점을 가질 수 있다는 것이 일반함수와의 차이다.

일반적인 함수나 메서드(루틴과 서브루틴)

코루틴

중단함수는 코루틴이 아니다.

코루틴을 처음 접하는 경우 중단함수가 코루틴이라고 착각할 수 있다.

중단함수는 코루틴이 아니고, 코루틴 내에서 실행될 수 있는 함수이다.

중단함수의 사용

중단함수는 코루틴을 중단할 수 있는 함수이다. 그렇기 때문에 일반함수처럼 사용할 수 없다.

중단함수를 호출할 수 있는 곳은 아래와 같다.

  • 코루틴 스코프
  • 다른 중단함수
fun main() {
	myMethod() // 호출 불가능
}

suspend fun myMethod() { }

// 호출 가능
fun main() = runBlocking {
    launch1()
}

// 호출 가능
suspend fun main()   {
    launch1()
}

또한 중단 함수내에서는 코루틴 빌더를 사용할 수 없다.

코루틴 빌더를 사용하기 위해서는 코루틴 스코프가 필요한데(코루틴 스코프의 확장함수), 중단 함수 내에서는 코루틴 스코프를 얻을 수 없기 때문이다.

따라서, 중단함수 내에서 코루틴 빌더를 사용하기 위해서는 코루틴 스코프 함수를 통해 스코프를 생성해줘야한다.

fun main() = runBlocking {
	myMethod() 
}
suspend fun myMethod() = coroutineScope {
	launch {}
}

이를 다이어그램으로 구조화된 동시성을 나타내면 아래와 같다.

중단한다는 것은 무엇을 의미하는가?

코루틴은 일반적으로 사용되는 루틴이나 서브루틴과 다르게 중단과 재개가 가능하다.

중단한다는 것은 중단함수를 호출한 코루틴을 중단하는 것이다.

이것은 스레드를 블로킹하는 것이 아니기 때문에, 해당 스레드는 다른 코루틴을 실행할 수 있다.

우선 코루틴의 중단에 대해 알아보기 전에, 루틴, 서브루틴에 대해 살펴보자.

일반적인 프로그래밍 언어에서 사용되는 메서드나 함수라고 봐도 무방하다.

fun main() {
	myMethod1()
	myMethod2()

}
// 서브 루틴
fun myMethod1() { 
	// 생략
}
// 서브 루틴
fun myMethod2() { 
	// 생략
}

이런 루틴은 한번 실행이될 때, 콜스택에 로드되어 실행이 끝날때까지 실행이되며

실행이 완료되어 콜스택에서 빠져나오는 경우 콜스택 내부의 상태가 사라진다.

하지만, 아래 코드를 실행해보자.

suspend fun main() = runBlocking {
		// 코루틴
    launch {
        while (true) {
            println("launch1")
            yield()
        }
    }
    // 코루틴
    launch {
        while (true) {
            println("launch2")
            yield()
        }
    }
    println("main end")
}
>> launch1
>> launch2
>> launch1
>> launch2
... 계속 반복

위 코드는 코루틴이 서로 양보(yield)를 하며, 실행을 중단하였다가 재개하였다가를 반복한다.

코루틴이 중단되는 것은 메서드가 종료되는 것이 아니라, 중단된 지점으로 다시 돌아와서 재개될 수 있음을 의미한다.

누구를 중단하는가?

그렇다면 중단함수에서 중단하는 경우, 누가 중단될까?

바로 중단함수를 호출한 코루틴이다. 다시한번 말하지만, 중단한다는 것은 스레드를 블로킹하는 것과는 다르다. 코루틴이 중단되어도, 해당 스레드는 다른 코루틴을 실행할 수 있다.

아래 예시에서 delay(1000) 부분에 대해서 설명하겠다.

suspend fun main() = runBlocking {
    launch {
        println("launch1 start")
        launch {
            println("launch2 start")
            delay(1000)
            println("launch2 end")
        }
        delay(50)
        println("launch1 end")
    }
    println("main end")
} 
>> main end
>> launch1 start
>> launch2 start
>> launch1 end
>> launch2 end
  1. main end 호출
  2. luanch1 호출
  3. delay(50)
  4. launch2 시작
  5. 2번째 launch 에서 delay(1000) 메서드를 통해 1초간 중단
  6. launch1 끝

5번에서 delay(1000) 실행시, launch2 가 중단되었다. 하지만, 바깥의 launch1 은 delay(50) 이후에 실행이 재개되었다.

실행흐름을 다이어그램으로 나타내면 아래와 같다.

#1 은 launch1, #2는 launch 2 이다.