๊นจ์•Œ ๊ฐœ๋… ๐Ÿ“‘/Kotlin

[Kotlin] Coroutines - ํ•œ Scope ๋‚ด์—์„œ์˜ ๊ณ„์ธต ๊ด€๊ณ„

interfacer_han 2024. 7. 31. 00:43

#1 ์ด์ „ ๊ธ€

[Kotlin] Coroutines - Coroutine builder

#1 Coroutine builder kotlinx-coroutines-coreCore primitives to work with coroutines. Coroutine builder functions: Coroutine dispatchers implementing CoroutineDispatcher: More context elements: Synchronization primitives for coroutines: Top-level suspendin

kenel.tistory.com

์œ„ ๊ฒŒ์‹œ๊ธ€์˜ CoroutineScope์˜ ์ƒ๋žต์— ๋Œ€ํ•ด ๋‹ค๋ฃฌ #6-4์—์„œ ์ด์–ด์ง€๋Š” ๊ธ€์ด๋‹ค. ์ด์ „ ๊ธ€์—์„  CoroutineScope์„ ์ƒ๋žตํ•˜๋Š” ๊ฒŒ, ๊ฐ€๋…์„ฑ ์ด์™ธ์˜ ์˜๋ฏธ๊ฐ€ ์žˆ๋‹ค๊ณ  ํ–ˆ๋‹ค. ๊ทธ๊ฒƒ์€ ๋ฐ”๋กœ ํ•˜๋‚˜์˜ CoroutineScope ๋‚ด Coroutine ๊ฐ„ ๋ถ€๋ชจ-์ž์‹ ๊ณ„์ธต์˜ ๊ตฌํ˜„์ด๋‹ค.
 
๊ทธ๋ฆฌ๊ณ  ์ด ํ•˜๋‚˜์˜ CoroutineScope ๋‚ด Coroutine ๊ฐ„ ๋ถ€๋ชจ-์ž์‹ ๊ณ„์ธต์˜ ๊ตฌํ˜„์€ join() ๋ฐ cancel()์—์„œ์˜ ์ด์ ์„ ๊ฐ€์ง„๋‹ค (join()์—์„œ์˜ ์ด์ ์€ join()๊ณผ ๊ฐ™์€ ๋งฅ๋ฝ์˜ ํ•จ์ˆ˜์ธ await()๋‚˜ receive()์—๋„ ์ ์šฉ๋œ๋‹ค).
 
์ด ์ด์ ๋“ค์„ ์„ค๋ช…ํ•˜๊ธฐ ์•ž์„œ, Coroutine ๊ฐ„ ๋ถ€๋ชจ-์ž์‹ ๊ณ„์ธต์ด ์–ด๋–ค ๋ชจ์–‘์œผ๋กœ ํ‘œํ˜„๋˜๋Š”์ง€๋ฅผ ๋จผ์ € ์งš๊ณ  ๋„˜์–ด๊ฐˆ ํ•„์š”๊ฐ€ ์žˆ๋‹ค.
 

#2 ๋‹จ์ผ Scope ๋‚ด ๋ณต์ˆ˜์˜ Coroutines ๊ฐ„ ๋ถ€๋ชจ-์ž์‹ ๊ณ„์ธต ํ˜•์„ฑ์˜ ์–‘์ƒ

#2-1 CoroutineScope๋ฅผ ์ƒ๋žตํ•˜์ง€ ์•Š์€ ์ฝ”๋“œ

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

suspend fun main() {
    val coroutine1 = CoroutineScope(Dispatchers.Default).launch {

        val coroutine2 = CoroutineScope(Dispatchers.Default).launch {
            delay(1000)
            println("coroutine2 is done.")
        }

        val coroutine3 = CoroutineScope(Dispatchers.Default).launch {
            delay(8000)
            println("coroutine3 is done.")
        }

        val coroutine4 = CoroutineScope(Dispatchers.Default).launch {
            delay(6000)
            println("coroutine4 is done.")
        }

        delay(4000)
        println("coroutine1 is done.")
    }

    coroutine1.join()
    println("End")
}

suspend ํ‚ค์›Œ๋“œ์— ๋Œ€ํ•ด ๋ชจ๋ฅธ๋‹ค๋ฉด ์ด ๊ฒŒ์‹œ๊ธ€์„ ์ฐธ์กฐํ•œ๋‹ค. ์œ„ ์ฝ”๋“œ๋Š” CoroutineScope ์•ˆ์— ๋˜ ๋‹ค๋ฅธ CoroutineScope๊ฐ€ ์œ„์น˜ํ•œ ์ฝ”๋“œ๋‹ค. ์ด ์ฝ”๋“œ๋ฅผ ๋„์‹๋„๋กœ ํ‘œํ˜„ํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.
 

์ด 4๊ฐœ์˜ ์ฝ”๋ฃจํ‹ด์ด ์ˆ˜ํ–‰๋˜๋ฉฐ, ๊ทธ ์ฝ”๋ฃจํ‹ด์€ ๊ฐ๊ฐ ๋‹ค๋ฅธ ์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„์— ๋“ค์–ด์žˆ๊ฒŒ ๋œ๋‹ค. ์ด ๋„์‹๋„์—์„œ coroutine2 ~ 4๊ฐ€ coroutine1์™€ ๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ์ด๋ฃฌ๋‹ค๊ณ  ์˜คํ•ดํ•ด์„œ๋Š” ์•ˆ ๋œ๋‹ค. coroutine2 ~ 4์€ coroutine1์˜ ์ฝ”๋“œ ๋‚ด์šฉ์— ์˜ํ•ด ์ƒ์„ฑ๋˜์—ˆ์„ ๋ฟ ์•„๋ฌด๋Ÿฐ ๊ด€๋ จ์ด ์—†๋‹ค (๋ฌผ๋ก  ํ˜ธ์ถœํ•จ-ํ˜ธ์ถœ๋จ์ด๋ผ๋Š” ๊ด€์ ์—์„œ๋Š” ๊ณ„์ธต ๊ตฌ์กฐ๋ฅผ ์ด๋ฃฌ๋‹ค๊ณ  ๋ณผ ์ˆ˜ ์žˆ์ง€๋งŒ, ์ผ๋ฐ˜์ ์œผ๋กœ ๊ณ„์ธต ๊ด€๊ณ„๋ฅผ ์˜๋ฏธํ•˜๋Š” ๋ถ€๋ชจ-์ž์‹ ๊ด€๊ณ„๋Š” ์•„๋‹ˆ๋ผ๋Š” ๋ง์ด๋‹ค).
 

#2-2 CoroutineScope๋ฅผ ์ƒ๋žตํ•œ ์ฝ”๋“œ

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch

suspend fun main() {
    val coroutine1 = CoroutineScope(Dispatchers.Default).launch {

        val coroutine2 = launch {
            delay(1000)
            println("coroutine2 is done.")
        }

        val coroutine3 = launch {
            delay(8000)
            println("coroutine3 is done.")
        }

        val coroutine4 = launch {
            delay(6000)
            println("coroutine4 is done.")
        }

        delay(4000)
        println("coroutine1 is done.")
    }

    coroutine1.join()
    println("End")
}

์ด์ „ ๊ธ€(#1)์˜ ๋‚ด์šฉ์— ๊ธฐ๋ฐ˜ํ•ด  Coroutine Builder์˜ ์ƒ๋žต์„ ๊ตฌํ˜„ํ•œ ์ฝ”๋“œ๋‹ค. ์ฒซ CoroutineScope ์™ธ์—๋Š” ์ถ”๊ฐ€์ ์ธ CoroutineScope๊ฐ€ ์“ฐ์ด์ง€ ์•Š์•˜๋‹ค. ์ด ์ฝ”๋“œ๋ฅผ ๋„์‹๋„๋กœ ํ‘œํ˜„ํ•˜๋ฉด ์•„๋ž˜์™€ ๊ฐ™๋‹ค.
 

#2-1์˜ ๋„์‹๋„์™€ ๋ถ„๋ช…ํ•œ ์ฐจ์ด๊ฐ€ ๋ณด์ธ๋‹ค. 4๊ฐœ์˜ ์ฝ”๋ฃจํ‹ด์ด ์ˆ˜ํ–‰๋˜๋Š” ๊ฒƒ์€ ๋™์ผํ•˜์ง€๋งŒ ๋ชจ๋‘ ํ•˜๋‚˜์˜ CoroutineScope๋ฅผ ๊ณต์œ ํ•˜๊ณ  ์žˆ๋‹ค. ์ฆ‰, CoroutineScope๋ฅผ ์ƒ๋žตํ•˜๋Š” ๊ฒƒ์˜ ์˜๋ฏธ๋Š” ๋‹จ์ˆœํžˆ ๊ฐ€๋…์„ฑ ํ–ฅ์ƒ์— ๊ทธ์น˜์ง€ ์•Š์œผ๋ฉฐ, ์ด๋ ‡๊ฒŒ ๋‹ค๋ฅธ ๊ตฌ์กฐ๋ฅผ ๋งŒ๋“ค์–ด๋‚ด๋Š” ๊ฒƒ์ด๋‹ค. ๊ทธ๋ฆฌ๊ณ  ์ด๋ ‡๊ฒŒ ์–ด๋–ค ์ฝ”๋ฃจํ‹ด ์Šค์ฝ”ํ”„ ๋‚ด์—์„œ ๋˜ ๋‹ค๋ฅธ ์ฝ”๋ฃจํ‹ด์„ CoroutineScope์„ ์ƒ๋žตํ•ด ํ˜ธ์ถœํ•˜๋ฉด, ํ˜ธ์ถœ๋œ coroutine์€ ํ˜ธ์ถœํ•œ coroutine์˜ ์ž์‹ coroutine์ด ๋œ๋‹ค.
 
๊ทธ๋ ‡๋‹ค๋ฉด, ์ฝ”๋ฃจํ‹ด์—์„œ ๋ถ€๋ชจ-์ž์‹์˜ ๊ณ„์ธตํ™”๋ฅผ ํ˜•์„ฑํ•˜๋Š” ๊ฒƒ์€ ์–ด๋–ค ์ด์ ์ด ์žˆ๋Š”๊ฐ€?
 

#3 ์ฝ”๋ฃจํ‹ด ๊ณ„์ธตํ™”์˜ ์ด์ 

#3-1 ์ผ๊ด„์  join()

CoroutineScope๋ฅผ ์ƒ๋žตํ•˜์ง€ ์•Š์€ #2-1์˜ ์ถœ๋ ฅ ๊ฒฐ๊ณผ
coroutine2 is done.
coroutine1 is done.
End

CoroutineScope๋ฅผ ์ƒ๋žตํ•œ #2-2์˜ ์ถœ๋ ฅ ๊ฒฐ๊ณผ
coroutine2 is done.
coroutine1 is done.
coroutine4 is done.
coroutine3 is done.
End

CoroutineScope๋ฅผ ์ƒ๋žตํ•˜์ง€ ์•Š์€ #2-1์˜ ์ถœ๋ ฅ ๊ฒฐ๊ณผ์™€, CoroutineScope๋ฅผ ์ƒ๋žตํ•œ #2-2์˜ ์ถœ๋ ฅ ๊ฒฐ๊ณผ๋‹ค. ๋‘ ์ฝ”๋“œ ๋ชจ๋‘ corotuine1.join()์„ ์ˆ˜ํ–‰ํ–ˆ์Œ์—๋„, ํ›„์ž์˜ ์ฝ”๋“œ์™€ ๋‹ฌ๋ฆฌ ์ „์ž๋Š” ์™„๋ฃŒ๋˜์ง€ ์•Š์€ ์ฝ”๋ฃจํ‹ด์ด ์กด์žฌํ•จ์„ ์ถœ๋ ฅ ๊ฒฐ๊ณผ๋ฅผ ํ†ตํ•ด ์•Œ ์ˆ˜ ์žˆ๋‹ค (ํ”„๋กœ๊ทธ๋žจ์ด ์ข…๋ฃŒ๋  ๋•Œ๊นŒ์ง€ ์™„๋ฃŒ๋˜์ง€ ์•Š์€ ์ฝ”๋ฃจํ‹ด์€ ์ถœ๋ ฅ์ด ๋ถˆ๊ฐ€๋Šฅํ•˜๋‹ค). ์ด๋Š” ๋ถ€๋ชจ ์ฝ”๋ฃจํ‹ด์˜ join()์„ ์ˆ˜ํ–‰ํ•˜๋ฉด, ์ž์‹ ์ฝ”๋ฃจํ‹ด๋„ ์•”์‹œ์ ์œผ๋กœ join()์ด ์ˆ˜ํ–‰๋˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์ฆ‰ ์ฝ”๋ฃจํ‹ด์˜ ๊ณ„์ธตํ™”๋ฅผ ์ž‘์—…ํ•ด๋‘๋ฉด, ์ผ์ผํžˆ ๋ชจ๋“  ํ•˜์œ„(์ž์‹) ์ฝ”๋ฃจํ‹ด์˜ join()์„ ์ˆ˜ํ–‰ํ•  ํ•„์š”๊ฐ€ ์—†์–ด์ง„๋‹ค (์ด join()์—์„œ์˜ ์ด์ ์€ join()๊ณผ ๊ฐ™์€ ๋งฅ๋ฝ์˜ ํ•จ์ˆ˜์ธ await()๋‚˜ receive()์—๋„ ์ ์šฉ๋œ๋‹ค).
 

#3-2 ์ผ๊ด„์  cancle()

import kotlinx.coroutines.*

suspend fun main() {
    val coroutine1 = CoroutineScope(Dispatchers.Default).launch {

        // coroutine1์˜ ์ž์‹
        val coroutine2 = launch {
            while (isActive) {
                println("coroutine2 is running... on ${Thread.currentThread().name}")
                delay(1000)
            }
        }

        // coroutine1์˜ ์ž์‹
        val coroutine3 = launch {
            while (isActive) {
                println("coroutine3 is running... on ${Thread.currentThread().name}")
                delay(1000)
            }
        }

        // ๋…๋ฆฝ์ ์ธ ์ฝ”๋ฃจํ‹ด (coroutine1์˜ ์ž์‹ ์•„๋‹˜)
        val coroutine4 = CoroutineScope(Dispatchers.Default).launch {
            while (isActive) {
                println("coroutine4 is running... on ${Thread.currentThread().name}")
                delay(1000)
            }
        }

        while (isActive) {
            println("coroutine1 is running... on ${Thread.currentThread().name}")
            delay(1000)
        }
    }

    delay(5000)
    coroutine1.cancel()

    delay(10000)
    println("End")
}

/* ์ถœ๋ ฅ ๊ฒฐ๊ณผ
coroutine2 is running... on DefaultDispatcher-worker-3
coroutine1 is running... on DefaultDispatcher-worker-1
coroutine3 is running... on DefaultDispatcher-worker-4
coroutine4 is running... on DefaultDispatcher-worker-1
coroutine2 is running... on DefaultDispatcher-worker-2
coroutine1 is running... on DefaultDispatcher-worker-3
coroutine3 is running... on DefaultDispatcher-worker-1
coroutine4 is running... on DefaultDispatcher-worker-4
coroutine2 is running... on DefaultDispatcher-worker-4
coroutine3 is running... on DefaultDispatcher-worker-3
coroutine1 is running... on DefaultDispatcher-worker-1
coroutine4 is running... on DefaultDispatcher-worker-2
coroutine2 is running... on DefaultDispatcher-worker-2
coroutine1 is running... on DefaultDispatcher-worker-3
coroutine4 is running... on DefaultDispatcher-worker-4
coroutine3 is running... on DefaultDispatcher-worker-1
coroutine2 is running... on DefaultDispatcher-worker-1
coroutine4 is running... on DefaultDispatcher-worker-3
coroutine3 is running... on DefaultDispatcher-worker-2
coroutine1 is running... on DefaultDispatcher-worker-4
coroutine4 is running... on DefaultDispatcher-worker-2
coroutine4 is running... on DefaultDispatcher-worker-2
coroutine4 is running... on DefaultDispatcher-worker-2
coroutine4 is running... on DefaultDispatcher-worker-2
coroutine4 is running... on DefaultDispatcher-worker-2
coroutine4 is running... on DefaultDispatcher-worker-2
coroutine4 is running... on DefaultDispatcher-worker-2
coroutine4 is running... on DefaultDispatcher-worker-2
coroutine4 is running... on DefaultDispatcher-worker-2
coroutine4 is running... on DefaultDispatcher-worker-2
End
*/

์ผ๊ด„์  join()๊ณผ ๊ฐ™์€ ๋งฅ๋ฝ์œผ๋กœ, ๋ถ€๋ชจ ์ฝ”๋ฃจํ‹ด์„ cancle()ํ•˜๋ฉด ์ž์‹ ์ฝ”๋ฃจํ‹ด๋“ค ๋˜ํ•œ ์ผ๊ด„์ ์œผ๋กœ cancle()๋œ๋‹ค.

coroutine2 ~ 3์€ coroutine1์˜ ์ž์‹์œผ๋กœ ๋’€์ง€๋งŒ, coroutine4๋Š” ๋ณ„๋„์˜ ๋…๋ฆฝ์ ์ธ ์ฝ”๋ฃจํ‹ด์œผ๋กœ ๋’€๋‹ค. ์ถœ๋ ฅ ๊ฒฐ๊ณผ๋Š” ๋ณด๋ฉด coroutine1์ด ์ทจ์†Œ๋˜๊ธฐ ์ „๊นŒ์ง€ ์ฆ‰ 5์ดˆ ๋™์•ˆ coroutine1 ~ 4 ๋ชจ๋‘๊ฐ€ ์‹คํ–‰๋œ๋‹ค. ํ•˜์ง€๋งŒ ๊ทธ ํ›„ coroutine1.cancel()์ด ์‹คํ–‰๋˜๊ณ , coroutine1๊ณผ ๊ทธ ์ž์‹์ธ coroutine2 ~ 3์ด ์•”์‹œ์ ์œผ๋กœ ์ทจ์†Œ๋œ๋‹ค. ์ดํ›„ coroutine4๋งŒ ์‹คํ–‰๋˜๋Š” ๋ชจ์Šต์„ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. coroutine4๋ฅผ ํ˜ธ์ถœํ•œ ๊ฒƒ์€ ๋ถ„๋ช… coroutine1์˜€์œผ๋‚˜, ๊ทธ๋ ‡๋‹ค๊ณ  coroutine4๊ฐ€ coroutine1์˜ ์ž์‹์ด ๋˜๋Š” ๊ฑด ์•„๋‹ˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
 
์—ฌ๋‹ด์œผ๋กœ, ์Šค๋ ˆ๋“œํ’€์˜ ์Šค๋ ˆ๋“œ๋‹ต๊ฒŒ ์ฝ”๋ฃจํ‹ด ํ•˜๋‚˜๊ฐ€ ์Šค๋ ˆ๋“œ ํ•˜๋‚˜๋ฅผ ๊ณ„์† ์ ์œ ํ•˜๋Š” ๊ฒŒ ์•„๋‹Œ, ๋ฐ˜๋‚ฉ๊ณผ ์žฌ์‚ฌ์šฉ์„ ๋ฐ˜๋ณตํ•˜๋Š” ๊นจ์•Œ๊ฐ™์€ ๋ชจ์Šต๋„ ์ถœ๋ ฅ ๊ฒฐ๊ณผ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค.
 

#4 ๋น„์Šทํ•œ ๊ธ€

[Kotlin] Coroutines - Structured Concurrency

#1 Unstructured Concurrency์ฝ”๋ฃจํ‹ด ์ฝ”๋“œ๋ฅผ ์„ค๊ณ„ํ•  ๋•Œ ์กฐ์‹ฌํ•ด์•ผ ํ•˜๋Š” ์ ์ด ์žˆ๋‹ค. ๋‹ค์Œ ์ฝ”๋“œ๋ฅผ ๋ณด์ž. #1-1 ์ฝ”๋“œimport kotlinx.coroutines.*class MyClass { suspend fun getMyCount(): Int { var count = 0 CoroutineScope(Dispatchers.IO).lau

kenel.tistory.com

์œ„ ๊ฒŒ์‹œ๊ธ€์€ ๋ณธ ๊ฒŒ์‹œ๊ธ€๊ณผ ๋น„์Šทํ•œ ๋‚ด์šฉ์„ ๋‹ค๋ฃฌ๋‹ค. ๋ฐ”๋กœ, ์•”์‹œ์  join()์˜ ์ˆ˜ํ–‰์— ๋Œ€ํ•œ ๋‚ด์šฉ์ด๋‹ค. ์ฐจ์ด์ ์ด๋ผ๋ฉด, ๋ณธ ๊ฒŒ์‹œ๊ธ€์—์„  ์ž์‹ ์ฝ”๋ฃจํ‹ด์˜ ์•”์‹œ์  join()์— ๋Œ€ํ•ด ๋‹ค๋ค˜๋‹ค๋ฉด, ์œ„ ๊ฒŒ์‹œ๊ธ€์—์„  ๋ถ€๋ชจ ์ฝ”๋ฃจํ‹ด์˜ ์•”์‹œ์  join()์— ๋Œ€ํ•ด ๋‹ค๋ฃฌ๋‹ค.