인프런 영문 브랜드 로고
인프런 영문 브랜드 로고

인프런 커뮤니티 질문&답변

denia park님의 프로필 이미지
denia park

작성한 질문수

코틀린 코루틴 완전 정복

섹션 요약

CoroutineDispatcher(Default, IO)의 limitedParallelism 관련 질문

해결된 질문

작성

·

98

·

수정됨

1

안녕하세요. 강사님

코루틴 강의를 1번 완강하고, 최근에는 전체적으로 강의를 복습을 하고 있습니다.

복습을 하다가 CoroutineDispatcherlimitedParallelism 에 대해 몇가지 궁금한 점이 생겨서 질문을 남기게 됐습니다.


Dispatchers.Default 관련

  1. Dispatchers.Default의 스레드풀 수를 넘어서는 숫자 혹은 스레드풀 수에 딱 맞게 스레드 수를 지정하여 limitedParallelism 함수를 실행하는 경우,
    Dispatchers.Default의 모든 스레드들은 제가 지정한 해당 작업을 처리하기 위해 계속 작업을 하고, 해당 작업이 시작된 이후에 Dispatchers.Default로 지정하여 실행한 코루틴 작업들은 제가 지정한 작업이 끝나기 전까지는 모두 대기하게 되는 것인가요??

    예를 들어, CPU 코어가 4개인 컴퓨터라면 Dispatchers.Default의 스레드 풀에 들어있는 스레드 수는 4개가 될테고, 해당 상황에서 Dispatchers.Default.limitedParallelism(4) 혹은 스레드가 몇개 있는지 몰라 Dispatchers.Default.limitedParallelism(8) 이렇게 지정하는 경우에

    아래 코드를 기준으로 하면 제가 먼저 시킨 작업(superCpuIntensiveTask())이 끝나기 전에는 lightCpuIntensiveTask()는 실행되지 않는 것일까요?
    (※ 멀티 스레드 작업이기 때문에 아래의 lightCpuIntensiveTask()가 먼저 실행될 가능성도 있지만 아주 재수가 나쁘게 superCpuIntensiveTask()가 먼저 실행이 된다면 어떻게 되는지가 궁금합니다)

// CPU 코어가 4개인 컴퓨터
fun main() = runBlocking {
    launch(Dispatchers.Default.limitedParallelism(8)) {
        superCpuIntensiveTask()
    }
    
    launch(Dispatchers.Default) {
        lightCpuIntensiveTask()
    }

    println("Done")
}

 

  1. 1번과 비슷한 질문입니다. CPU 코어가 4개인 컴퓨터에서 Dispatchers.Default.limitedParallelism를 여러번 사용한 경우, 코드상으로 보면 사용해야 하는 스레드의 수가 전체 스레드풀 수를 넘어서는 숫자가 되는데 이 경우에는 어떻게 스레드를 나눠서 사용하는 걸까요 ?

// CPU 코어가 4개인 컴퓨터
fun main() = runBlocking {
    launch(Dispatchers.Default.limitedParallelism(3)) {
        superCpuIntensiveTask()
    }
    
    launch(Dispatchers.Default.limitedParallelism(3)) {
        superCpuIntensiveTask2()
    }

    launch(Dispatchers.Default.limitedParallelism(2)) {
        CpuIntensiveTask()
    }

    println("Done")
}

Dispatchers.IO 관련

  1. Dispatchers.IO.limitedParallelism를 사용하면 새로운 Thread 집합을 만든다고 말씀해주셨습니다.

    그림에서 표현해주신 것처럼 기존에는 개별로 존재하는 Thread들을 새로 그룹으로 묶어낸다고 이해를 하면 되는 것일까요 ??

    Dispatchers.IO.limitedParallelism(2)

     

  2. Dispatchers.IO.limitedParallelism으로 묶여있던 Thread들은 해당 코루틴이 끝나면 다시 그룹이 풀리는 것일까요 ??


  3. 만약에 Dispatchers.IO.limitedParallelism를 많이 사용하여 이미 기존의 스레드들 모두가 집합으로 구성이 되어있는 경우에는 신규로 집합을 만들 수가 없는 경우에는 해당 Dispatchers.IO.limitedParallelism 요청이 어떻게 되는지 궁금합니다.


limitedParallelism의 Thread 수 지정

  1. 해당 코드와 같이 Dispatchers.Default.limitedParallelism(4) Thread 수를 지정해야 할때, 어떤 기준으로 어떻게 Thread 수를 지정해야 좋을까요 ?? 강사님만의 팁이 있으시면 공유가 가능하실까요??

    어떻게 수를 정해서 넣어야 할지 감이 잘 잡히지 않습니다. 😢

  2. 1번과 관련된 질문입니다. Thread 수를 지정할 때 현재 Thread Pool에 존재하거나 남아있는 쓰레드가 몇개인지 충분히 고려를 하면서 코드를 짜야할까요 ??
    실수로 스레드 풀의 전체 Thread 수를 넘어선 요청을 하게 되면 어떻게 되는지 궁금합니다.

 

 

질문이 많아서 정말 죄송합니다. 🙇‍♂🙇‍♂🙇‍♂

답변 1

2

조세영님의 프로필 이미지
조세영
지식공유자

denia park님 안녕하세요. 지식 공유자 조세영입니다.

상세한 질문을 남겨 주셔서 감사합니다. 아래는 답변입니다.


Dispatchers.Default 관련

// CPU 코어가 4개인 컴퓨터
fun main() = runBlocking {
    launch(Dispatchers.Default.limitedParallelism(8)) {
        superCpuIntensiveTask()
    }
    
    launch(Dispatchers.Default) {
        lightCpuIntensiveTask()
    }

    println("Done")
}
  1. Dispatchers.Default의 스레드풀 수를 넘어서는 숫자 혹은 스레드풀 수에 딱 맞게 스레드 수를 지정하여 limitedParallelism 함수를 실행하는 경우,
    Dispatchers.Default의 모든 스레드들은 제가 지정한 해당 작업을 처리하기 위해 계속 작업을 하고, 해당 작업이 시작된 이후에 Dispatchers.Default로 지정하여 실행한 코루틴 작업들은 제가 지정한 작업이 끝나기 전까지는 모두 대기하게 되는 것인가요??

    예를 들어, CPU 코어가 4개인 컴퓨터라면 Dispatchers.Default의 스레드 풀에 들어있는 스레드 수는 4개가 될테고, 해당 상황에서 Dispatchers.Default.limitedParallelism(4) 혹은 스레드가 몇개 있는지 몰라 Dispatchers.Default.limitedParallelism(8) 이렇게 지정하는 경우에

    아래 코드를 기준으로 하면 제가 먼저 시킨 작업(superCpuIntensiveTask())이 끝나기 전에는 lightCpuIntensiveTask()는 실행되지 않는 것일까요?
    (※ 멀티 스레드 작업이기 때문에 아래의 lightCpuIntensiveTask()가 먼저 실행될 가능성도 있지만 아주 재수가 나쁘게 superCpuIntensiveTask()가 먼저 실행이 된다면 어떻게 되는지가 궁금합니다)

 

// CPU 코어가 4개인 컴퓨터
fun main() = runBlocking {
    launch(Dispatchers.Default.limitedParallelism(3)) {
        superCpuIntensiveTask()
    }
    
    launch(Dispatchers.Default.limitedParallelism(3)) {
        superCpuIntensiveTask2()
    }

    launch(Dispatchers.Default.limitedParallelism(2)) {
        CpuIntensiveTask()
    }

    println("Done")
}

답변

코루틴은 실행 순서가 보장되지 않습니다. 뒤의 launch로 생성된 코루틴이 먼저 스레드를 점유해 실행될 수도 있어 실행 순서를 보장하려면 join을 써주셔야 합니다. 이 부분은 CPU Intensive Task이더라도 마찬가지입니다.

 

  1. 1번과 비슷한 질문입니다. CPU 코어가 4개인 컴퓨터에서 Dispatchers.Default.limitedParallelism를 여러번 사용한 경우, 코드상으로 보면 사용해야 하는 스레드의 수가 전체 스레드풀 수를 넘어서는 숫자가 되는데 이 경우에는 어떻게 스레드를 나눠서 사용하는 걸까요 ?

답변

Dispatchers.Default.limitedParallelism은 특정 작업을 위해 사용할 스레드를 제한하는 역할을 하는 것입니다. 여러 번 사용하더라도 4개의 스레드 중 일부로 제한되는 과정을 거치게 됩니다.


 

Dispatchers.IO 관련

  1. Dispatchers.IO.limitedParallelism를 사용하면 새로운 Thread 집합을 만든다고 말씀해주셨습니다.

    그림에서 표현해주신 것처럼 기존에는 개별로 존재하는 Thread들을 새로 그룹으로 묶어낸다고 이해를 하면 되는 것일까요 ??

    Dispatchers.IO.limitedParallelism(2)

     

답변

더욱 정확히는 스레드를 새로 생성하는 것이라고 이해해주시면 좋을 것 같습니다. 스레드는 비싼 자원이기 때문에 미리 정의된 Dispatcher(Dispatchers.IO)는 처음부터 모든 스레드를 생성해 놓는 것이 아닌, 필요할 때 스레드를 생성하는 방식을 택합니다.

  1. Dispatchers.IO.limitedParallelism으로 묶여있던 Thread들은 해당 코루틴이 끝나면 다시 그룹이 풀리는 것일까요 ??

답변

Dispatchers.IO.limitedParallelism로 생성된 디스패처가 메모리에 있는 동안은 그룹이 유지됩니다. 스레드는 작업이 없다면 정리될 수 있습니다.

 

  1. 만약에 Dispatchers.IO.limitedParallelism를 많이 사용하여 이미 기존의 스레드들 모두가 집합으로 구성이 되어있는 경우에는 신규로 집합을 만들 수가 없는 경우에는 해당 Dispatchers.IO.limitedParallelism 요청이 어떻게 되는지 궁금합니다.

답변

Dispatchers.IO.limitedParallelism는 스레드를 무제한으로 생성할 수 있습니다. 다만 이렇게 무제한으로 생성할 경우 메모리에 문제가 생길 수 있습니다.


limitedParallelism의 Thread 수 지정

  1. 해당 코드와 같이 Dispatchers.Default.limitedParallelism(4) Thread 수를 지정해야 할때, 어떤 기준으로 어떻게 Thread 수를 지정해야 좋을까요 ?? 강사님만의 팁이 있으시면 공유가 가능하실까요??

     

    답변
    먼저 개인적으로는 꼭 필요한 경우가 아니면 지정하지 않는 편입니다. 가끔 동영상 처리나 이미지 처리를 위해 사용할 스레드 수 제한이 꼭 필요한 경우가 있는데요. 그때는 작업의 중요도에 따라 다르게 설정하기 때문에 딱 몇개라고 말씀 드리기가 어렵습니다.

  2. 1번과 관련된 질문입니다. Thread 수를 지정할 때 현재 Thread Pool에 존재하거나 남아있는 쓰레드가 몇개인지 충분히 고려를 하면서 코드를 짜야할까요 ??
    실수로 스레드 풀의 전체 Thread 수를 넘어선 요청을 하게 되면 어떻게 되는지 궁금합니다.

답변

JVM 스레드는 OS 레벨의 스레드에 별도로 스캐쥴링 되는 과정을 거칩니다. 이 때문에 스레드를 너무 많이 생성하는 것이 아닌 이상, Thread 수 지정 시 스레드의 개수가 문제가 될 가능성은 적을 것 같습니다.

 

답변이 도움이 되었으면 좋을 것 같습니다. 질문 주신 내용들 중 수업에서 다룬 범위를 넘어가는 내용이 있어 답변의 키워드를 바탕으로 추가로 학습이 필요하실 수 있습니다. 참고 부탁드립니다.

감사합니다.

denia park님의 프로필 이미지
denia park
질문자

답변 정말 감사합니다!

 

제가 이해가 잘 안되는 부분이 있어서 그런데, 혹시 해당 내용에 관해서 조금만 더 구체적으로 알려주실 수 있으실까요 ??

 

Dispatchers.Default 관련 추가 질문

1. [1번 항목에 대한 추가 질문입니다.] 아래의 코드가 실행이 되면, 여러 개의 코루틴이 동시에 실행이 될텐데 아주 만약에 먼저 실행된 코루틴이 limitedParallelism(8) 혹은 limitedParallelism(6)인 경우 모든 Default의 스레드를 다 사용하기 때문에 나머지 코루틴들은 실행가능한 Default 스레드가 없어서 먼저 시작된 코루틴이 종료되기까지 계속 기다려야 하는게 맞나요 ??
(코루틴들의 실행 순서보다는 스레드가 모두 사용되면 뒤의 코루틴들은 계속 기다려야 하는지가 궁금합니다.)

// CPU 코어가 4개인 컴퓨터
fun main() = runBlocking {
    launch(Dispatchers.Default.limitedParallelism(8)) {
        superCpuIntensiveTask()
    }
    
    launch(Dispatchers.Default) {
        lightCpuIntensiveTask()
    }

    launch(Dispatchers.Default.limitedParallelism(6)) {
        superCpuIntensiveTask2()
    }

    println("Done")
}

 

  1. [2번 항목에 대한 추가 질문입니다.] 아래의 코드를 보면 여러번 limitedParallelism가 호출이 되는데, 아무리 많이 호출해도 Default 전체 스레드 중 일부로 제한이 된다는 것은 이해를 했습니다.

    그런데 제가 추가적으로 궁금한 점은 limitedParallelism(2)가 먼저 실행되는 경우 뒤에 오는 limitedParallelism(3)의 스레드 요청 수가 만족되지 못하고, limitedParallelism(3)가 먼저 실행이 되면 limitedParallelism(2)의 스레드 요청 수를 만족하지 못하는데
    이런 경우에 뒤에 실행되는 limitedParallelism는 해당 작업 코루틴이 실행되지 않고 대기를 하는 것인지, 남은 스레드 가지고 일단 실행을 하다가 추가적으로 스레드 할당이 되면 그때 스레드를 더 할당해서 동작을 하는 것인지 궁금합니다.

// CPU 코어가 4개인 컴퓨터
fun main() = runBlocking {
    launch(Dispatchers.Default.limitedParallelism(3)) {
        superCpuIntensiveTask()
    }
    
    launch(Dispatchers.Default.limitedParallelism(3)) {
        superCpuIntensiveTask2()
    }

    launch(Dispatchers.Default.limitedParallelism(2) {
        CpuIntensiveTask()
    }

    println("Done")
}
조세영님의 프로필 이미지
조세영
지식공유자

안녕하세요. 두 개 모두 같은 답변을 드릴 수 있을 것 같은데요.

Dispatchers.Default.limitedParallelism은 특정 작업을 위한 스레드를 제한하기 위한 것이고, 스레드를 배타적으로 사용하기 위한 함수가 아닙니다. 따라서 Dispatchers.Default가 8개의 스레드를 생성할 수 있다고 했을 때, Dispatchers.Default.limitedParallelism(8)이 호출된다고 하더라도 Dispatchers.Default를 사용하면 8개의 스레드를 여전히 사용할 수 있습니다. 마찬가지로 Dispatchers.Default.limitedParallelism(8)가 연속 두 번 호출되면 전체 스레드(8개) 를 사용하는 디스패처가 2개 생성되는 것일 뿐 입니다.

이것을 확인해보시기 위해서는 다음 코드를 실행해보시면 좋을 것 같습니다.

fun main() = runBlocking<Unit> {
    repeat(100) { repeat ->
        launch(Dispatchers.Default.limitedParallelism(8)) {
            repeat(100) {
                launch {
                    Thread.sleep(100L)
                    println("[${repeat}][${Thread.currentThread().name}] 코루틴 실행")
                }
            }
        }
    }
}

 

답변이 도움이 되셨으면 좋을 것 같습니다.

 

감사합니다.

denia park님의 프로필 이미지
denia park
질문자

코드를 동작시켜보니, 실행 순서에 상관없이 작업들이 분배되서 동작이 되네요.

 

[0][DefaultDispatcher-worker-8 @coroutine#5] 코루틴 실행
[0][DefaultDispatcher-worker-6 @coroutine#4] 코루틴 실행
[1][DefaultDispatcher-worker-10 @coroutine#10] 코루틴 실행
[0][DefaultDispatcher-worker-11 @coroutine#7] 코루틴 실행
[4][DefaultDispatcher-worker-9 @coroutine#15] 코루틴 실행
[2][DefaultDispatcher-worker-5 @coroutine#116] 코루틴 실행
[8][DefaultDispatcher-worker-15 @coroutine#331] 코루틴 실행
[9][DefaultDispatcher-worker-16 @coroutine#516] 코루틴 실행
[6][DefaultDispatcher-worker-13 @coroutine#117] 코루틴 실행
[4][DefaultDispatcher-worker-7 @coroutine#93] 코루틴 실행
[7][DefaultDispatcher-worker-14 @coroutine#220] 코루틴 실행
[5][DefaultDispatcher-worker-12 @coroutine#59] 코루틴 실행
[3][DefaultDispatcher-worker-4 @coroutine#25] 코루틴 실행
[1][DefaultDispatcher-worker-3 @coroutine#166] 코루틴 실행
[0][DefaultDispatcher-worker-1 @coroutine#41] 코루틴 실행
[10][DefaultDispatcher-worker-2 @coroutine#1109] 코루틴 실행
[4][DefaultDispatcher-worker-7 @coroutine#102] 코루틴 실행

 

limitedParallelism를 사용하면 사용할 스레드를 점유한다는 개념으로 이해를 해서 헷갈린 것 같습니다.

 

감사합니다!

denia park님의 프로필 이미지
denia park

작성한 질문수

질문하기