본문 바로가기

코루틴

[코루틴] 코루틴의 스레드 동작

코루틴은

스레드 사용이 필요 없어지면 스레드 양보
스레드를 양보하는 주체

 

CoroutineDispatcher는

스레드에 코루틴을 할당

 

코루틴이 스레드를 양보하려면 스레드 양보 함수를 호출해야 한다

그러지 않으면 완료될 때까지 스레드를 점유

 

코루틴에서 스레드를 양보하는 방법

delay
join, await
yield

 

delay 일시 중단 함수

fun main() = runBlocking<Unit> {
    val startTime = System.currentTimeMillis()
    repeat(10) { repeatTime ->
        launch {
            // delay로 1초 동안 메인 스레드 사용하도록 양보
            delay(1000L)
            // Thread.sleep(1000L)을 사용하면 10초 걸려서 모두 실행
            println("[${getElapsedTime(startTime)}] 코루틴${repeatTime} 실행 완료")
        }
    }
}
// 결과
[1.012초] 코루틴0 실행 완료
[1.013초] 코루틴1 실행 완료
[1.013초] 코루틴2 실행 완료
...
...
[1.013초] 코루틴9 실행 완료


// delay
// Delays coroutine for a given time without blocking a thread and resumes it after a specified time
public suspend fun delay(timeMillis: Long) {
    if (timeMillis <= 0) return // don't delay
    return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
        // if timeMillis == Long.MAX_VALUE then just wait forever like awaitCancellation, don't schedule.
        if (timeMillis < Long.MAX_VALUE) {
            cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
        }
    }
}

// Thread.sleep()
// The thread does not lose ownership of any monitors
public static void sleep(long millis) throws InterruptedException {
    sleep(millis, 0);
}

 

 

join, await 사용

위 함수를 호출한 코루틴은 스레드를 양보하고 join, await의 대상이 된 코루틴 내부의 코드가 실행 완료될 때까지 일시 중단

 

join 사용

fun main() = runBlocking<Unit> {
    val job = launch {
        println("1. launch 코루틴 작업 시작")
        delay(1000L)
        println("2. launch 코루틴 작업 완료")
    }
    println("3. runBlocking 코루틴은 곧 일시 중단되고 메인 스레드 양보")
    job.join() // job 내부 코드 모두 실행될 때까지 메인 스레드 일시 중단
    println("4. runBlocking이 메인 스레드에 분배되어 작업 다시 재개")
}
// 결과
3. runBlocking 코루틴은 곧 일시 중단되고 메인 스레드 양보
1. launch 코루틴 작업 시작
2. launch 코루틴 작업 완료
4. runBlocking이 메인 스레드에 분배되어 작업 다시 재개
  1. runBlocking 코루틴이 메인 스레드 점유
  2. runBlocking은 job.join 실행 시 메인 스레드 양보
  3. launch 실행
  4. delay 호출 후 메인 스레드 양보
  5. join에 의해 launch가 완료될 때까지 재개 X
  6. launch 코루틴 완료 후 runBlocking 재개

 

yield 사용

스레드 양보를 직접 호출

delay나 join은 스레드 양보를 직접 호출하지 않고 내부적으로 스레드 양보

 

yield를 사용하지 않은 경우

fun main() = runBlocking<Unit> {
    val job = launch {
        while (this.isActive) {
            println("작업 중")
        }
    }
    delay(100L)
    job.cancel()
}
// "작업 중" 계속 출력
// delay 호출 시 launch 코루틴이 메인 스레드 계속 점유. cancel은 호출되지 않음

 

yield를 사용한 경우

fun main() = runBlocking<Unit> {
    val job = launch {
        while (this.isActive) {
            println("작업 중")
            yield() // 명시적으로 양보
        }
    }
    delay(100L)
    job.cancel()
}
// 1초 후 cancel 호출

 

 

코루틴의 실행 스레드

코루틴의 실행 스레드는 가변적이다

fun main() = runBlocking<Unit> {
    val dispatcher = newFixedThreadPoolContext(2, "MyThread")
    launch(dispatcher) {
        repeat(3) {
            println("[${Thread.currentThread().name}] 코루틴 실행 일시 중단")
            delay(100L)
            println("[${Thread.currentThread().name}] 코루틴 실행 재개")
        }
    }
}
// 결과
[MyThread-1] 코루틴 실행 일시 중단
[MyThread-2] 코루틴 실행 재개
[MyThread-2] 코루틴 실행 일시 중단
[MyThread-2] 코루틴 실행 재개
[MyThread-2] 코루틴 실행 일시 중단
[MyThread-2] 코루틴 실행 재개

 

1. 코루틴 일시 중단 -> Thread-1은 다른 코루틴에 의해 점유 가능

일시 중단 된 코루틴

 

2. 새로운 코루틴 요청 -> Thread-1이나 Thread-2에 할당 가능 (Thread-1에 할당 가정)

새로운 코루틴 실행 요청

 

3. 일시 중단되었던 코루틴 재개 -> 작업 대기열로 이동 -> Dispatcher에 의해 Thread-2로 보내짐

    (Thread-1은 다른 코루틴 실행 중)

코루틴의 재개

 

 

스레드를 양보하지 않으면 실행 스레드 변경 X

실행 스레드가 바뀌는 시점은 코루틴이 재개될 때

양보를 하지 않으면 실행 스레드가 변경되지 않음

// Thread는 스레드 양보 X, 블로킹
fun main() = runBlocking<Unit> {
    val dispatcher = newFixedThreadPoolContext(2, "MyThread")
    launch(dispatcher) {
        repeat(3) {
            println("[${Thread.currentThread().name}] 코루틴 실행 일시 중단")
            Thread.sleep(100L)
            println("[${Thread.currentThread().name}] 코루틴 실행 재개")
        }
    }
}
// 결과
[MyThread-1] 코루틴 실행 일시 중단
[MyThread-1] 코루틴 실행 재개
[MyThread-1] 코루틴 실행 일시 중단
[MyThread-1] 코루틴 실행 재개
[MyThread-1] 코루틴 실행 일시 중단
[MyThread-1] 코루틴 실행 재개