깨알 개념/Kotlin

[Kotlin] Coroutines - Parallel Decomposition

interfacer_han 2024. 2. 17. 11:41

#1 작업의 처리 방식

#1-1 가정

위와 같은 총 7개의 작업이 있다고 가정한다. 각 작업이 소요 시간은 5, 10, 15, 10, 20, 25, 5다.

 

#1-2 전통적인 방법

전통적인 방법은 선형적(Serial)으로 작업을 수행한다.

 

#1-3 전통적인 방법의 구현

import kotlinx.coroutines.*

data class Food(val name: String, val cookingTimeInSeconds: Long)

suspend fun cook(food: Food) {
    delay(1000 * food.cookingTimeInSeconds)
    println("${food.name} 완료")
}

fun main() {
    val foods = arrayOf(
        Food("국밥", 5),
        Food("만두", 10),
        Food("비빔밥", 15),
        Food("햄버거", 10),
        Food("치킨", 20),
        Food("피자", 25),
        Food("샌드위치", 5),
    )

    val startTime = System.currentTimeMillis()

    runBlocking {
        for (food in foods) {
            cook(food)
        }
    }

    val endTime = System.currentTimeMillis()

    println("총 소요 시간: ${(endTime - startTime) / 1000.0}초")
}

/* 출력 결과
국밥 완료
만두 완료
비빔밥 완료
햄버거 완료
치킨 완료
피자 완료
샌드위치 완료
총 소요 시간: 90.168초
*/

전통적인 방식의 작업 처리를 코드로 나타내면 위와 같다.

 

#1-4 Parallel Decomposition

Parallel Decomposition은 문제를 작은 부분으로 나누어 동시에 처리한다. 7개의 작업 중 가장 오래 걸리는 작업이 곧 전체 소요 시간이 된다. 이 방법은 전체 작업의 시간을 단축시킨다. 다수의 CPU 코어를 효율적으로 활용해 대규모 문제를 나눠 해결한 것이다. 코틀린에서는 코루틴을 이용해 문제를 병렬로 나열함으로써 Parallel Decomposition을 구현할 수 있다.

 

#1-5 Parallel Decomposition의 구현

import kotlinx.coroutines.*

data class Food(val name: String, val cookingTimeInSeconds: Long)

suspend fun cook(food: Food) {
    delay(1000 * food.cookingTimeInSeconds)
    println("${food.name} 완료")
}

fun main() {
    val foods = arrayOf(
        Food("국밥", 5),
        Food("만두", 10),
        Food("비빔밥", 15),
        Food("햄버거", 10),
        Food("치킨", 20),
        Food("피자", 25),
        Food("샌드위치", 5),
    )

    val startTime = System.currentTimeMillis()

    runBlocking {
        val cookingJobs: ArrayList<Job> = ArrayList()

        for (food in foods) {
            cookingJobs.add(launch {
                cook(food)
            })
        }
        
/* runBlocking의 특성 덕에 생략 가능. 설명 참조.
        for(cookingJob in cookingJobs) {
            cookingJob.join()
        }
*/
    }

    val endTime = System.currentTimeMillis()

    println("총 소요 시간: ${(endTime - startTime) / 1000.0}초")
}

/* 출력 결과
국밥 완료
샌드위치 완료
만두 완료
햄버거 완료
비빔밥 완료
치킨 완료
피자 완료
총 소요 시간: 25.104초
*/

Parallel Decomposition 방식의 작업 처리를 코드로 나타내면 위와 같다. 코루틴을 사용했다. for문의 반복 횟수만큼 코루틴이 병렬적으로 수행된다. 가장 큰 Food.cookingTimeInSeconds는 곧 모든 코루틴이 완료되는 시간이 된다. Job.join() 함수를 생략한 이유는 runBlocking 함수는 안에 있는 모든 코루틴이 끝나야 종료(이 링크의 #3-2 참조)되기 때문이다.

 

#2 요약

문제를 나눠서 푸는 건 누구나 할 수 있다. 하지만, 나눈 걸 병렬적으로 푼다면 얘기가 달라진다.