๊นจ์•Œ ๊ฐœ๋… ๐Ÿ“‘/๊ธฐํƒ€

[Kotlin] Coroutines Flow - Cold Flow์™€ Hot Flow (SharedFlow)

interfacer_han 2024. 8. 7. 08:57

#1 Cold Flow์™€ Hot Flow

#1-1 ๊ฐœ์š”

๋ฐ์ดํ„ฐ ์ŠคํŠธ๋ฆผ์€ ํฌ๊ฒŒ Cold Flow์™€ Hot Flow๋กœ ๋‚˜๋ˆŒ ์ˆ˜ ์žˆ๋‹ค. ์ด ๋ถ„๋ฅ˜ ๊ธฐ์ค€์— ๋Œ€ํ•ด ์•Œ์•„๋ณธ๋‹ค. ๋˜, Kotlin์˜ Coroutines Flow๋ฅผ ํ™œ์šฉํ•ด ๊ฐ„๋‹จํ•œ Cold Flow ๋ฐ Hot Flow๋ฅผ ๊ตฌํ˜„ํ•ด๋ณธ๋‹ค. 
 

#1-2 ํ›Œ๋ฅญํ•œ ๋น„์œ 

 

What is the hot flow and cold flow in coroutines and the difference between them?

I am mastering Kotlin coroutines and trying to figure out 1- what is hot flow and cold flow ? 2- what is the main difference between them? 3- when to use each one?

stackoverflow.com

์œ„ ๋งํฌ๋Š” Cold Flow์™€ Hot Flow์˜ ์ฐจ์ด๋ฅผ ๋ฌป๋Š” ์งˆ๋ฌธ๊ธ€์ด๋‹ค. ์ด ์งˆ๋ฌธ์— ๋Œ€ํ•œ ๋‹ต๋ณ€ ์ค‘ ์•„์ฃผ ํ›Œ๋ฅญํ•œ ๋น„์œ ๋ฅผ ํ•œ ๋‹ต๋ณ€์ด ์žˆ๋‹ค. ๊ทธ ๋‹ต๋ณ€์„ ๋ฒˆ์—ญํ•˜๋ฉด,
 

์ฝœ๋“œ ํ”Œ๋กœ์šฐ(Cold flow): ๋ฆฌ์Šค๋„ˆ(Listener)๊ฐ€ ์กด์žฌํ•ด์•ผ๋งŒ ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฐฉ์ถœ๋จ.

์‹ค์ƒํ™œ ์˜ˆ์‹œ
๋‹น์‹ ์€ ๊ฑฐ์‹ค TV๋กœ ์ŠคํŒŒ์ด๋”๋งจ: ์–ดํฌ๋กœ์Šค ๋” ์œ ๋‹ˆ๋ฒ„์Šค๋ฅผ ๋ณด๋ ค๊ณ  ํ•œ๋‹ค. ๊ฑฐ์‹ค TV๋Š” IPTV๋ผ์„œ, ์ด ์˜ํ™”๋Š” ๋‚ด๊ฐ€ ์›ํ•  ๋•Œ ์ฆ‰ ์ƒํ’ˆ์„ ๊ฒฐ์ œํ•ด์„œ ์žฌ์ƒ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅผ ๋•Œ ์ƒ์˜๋œ๋‹ค.

ํ•ซ ํ”Œ๋กœ์šฐ(Hot flow): ๋ฐ์ดํ„ฐ๊ฐ€ ๋ฆฌ์Šค๋„ˆ(Listener)์˜ ์กด์žฌ ์—ฌ๋ถ€์™€ ๊ด€๊ณ„์—†์ด ๋ฐฉ์ถœ๋จ.

์‹ค์ƒํ™œ ์˜ˆ์‹œ
๋‹น์‹ ์€ ์˜ํ™” ๋ฐ”๋ฒคํ•˜์ด๋จธ๋ฅผ ๋ณด๋Ÿฌ ๊ฐ€๋ ค๊ณ  ์˜ํ™”๊ด€์œผ๋กœ ์ฐจ๋ฅผ ๋ชฐ์•˜๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ๊ฐ€๋Š” ๋„์ค‘ ๊ตํ†ต ์ฒด์ฆ์— ๊ฑธ๋ ค, ์ œ ์‹œ๊ฐ„์— ๋„์ฐฉํ•˜์ง€ ๋ชปํ–ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๊ณ  ์ง€๊ฐํ•œ ๋‹น์‹ ์„ ์œ„ํ•ด ์˜ํ™” ์‹œ์ž‘์ด ๋ฏธ๋ค„์ง€๋Š” ์ผ์€ ์—†์„ ๊ฒƒ์ด๋‹ค.

Cold Flow๋Š” VOD์™€ ๊ฐ™๊ณ , Hot Flow๋Š” ์˜ํ™”๊ด€๊ณผ ๊ฐ™๋‹ค. ์ค‘์š”ํ•œ ํ‚ค์›Œ๋“œ๋Š” ๋ฆฌ์Šค๋„ˆ์˜ ์กด์žฌ๋‹ค. ๋‚ด๊ฐ€ ๊ด€์ฐฐํ•ด์•ผ๋งŒ ์ง„ํ–‰๋˜๋Š” ๋ฐ์ดํ„ฐ ์ŠคํŠธ๋ฆผ์€ ์ฝœ๋“œ ํ”Œ๋กœ์šฐ๋‹ค. ๋ฐ˜๋ฉด, ๋‚ด๊ฐ€ ์—†์–ด๋„ ์•Œ์•„์„œ ์ฐฉ์ฐฉ ์ง„ํ–‰๋˜๋Š” ๋ฐ์ดํ„ฐ ์ŠคํŠธ๋ฆผ์€ ํ•ซ ํ”Œ๋กœ์šฐ๋‹ค.
 

#1-3 ๊ฐ๊ฐ์˜ ์šฉ๋„

Cold Flow ๋ฐ Hot Flow๋Š” ๊ทธ ํŠน์„ฑ์— ๋งž๊ฒŒ, ์„œ๋กœ ์‚ฌ์šฉ๋˜๋Š” ์šฉ๋„๊ฐ€ ๋‹ค๋ฅด๋‹ค. Cold Flow๋Š” ๋ฆฌ์Šค๋„ˆ(Listener, ๋˜๋Š” Subcriber) ๊ฐœ๊ฐœ์ธ์ด ๋ณด๋‚ด๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค Query ์š”์ฒญ์ด ๋Œ€ํ‘œ์ ์ด๋‹ค. Hot Flow๋Š” ์˜จ๋ผ์ธ RPG ๊ฒŒ์ž„์—์„œ ํ•„๋“œ์— ์ƒ์„ฑ(๋ฆฌ์  )๋˜๋Š” ๋ชฌ์Šคํ„ฐ๋ฅผ ์˜ˆ๋กœ ๋“ค ์ˆ˜ ์žˆ๋‹ค. ๋‚ด๊ฐ€ ํ•„๋“œ๋กœ ๋‚˜๊ฐ€์ง€ ์•Š๊ณ  ๋งˆ์„์—๋งŒ ์„œ์žˆ์„์ง€๋ผ๋„, ํ•„๋“œ์—๋Š” ๋ชฌ์Šคํ„ฐ๊ฐ€ ์ฐจ ์žˆ์–ด์•ผ ํ•œ๋‹ค. ๋‹ค๋ฅธ ์‚ฌ๋žŒ๋“ค๊ณผ ๋™์‹œ ์ ‘์†ํ•˜๋Š” ์˜จ๋ผ์ธ RPG๋‹ˆ๊นŒ ๋ง์ด๋‹ค

 

์—ฌ๋‹ด

๋”๋ณด๊ธฐ
๋”๋ณด๊ธฐ

์š”์ฆ˜์—๋Š” ์„œ๋ฒ„์˜ ์ž์› ๋‚ญ๋น„๋ฅผ ๋ง‰๊ธฐ ์œ„ํ•ด์„œ ํ•„๋“œ์— ํ”Œ๋ ˆ์ด์–ด๊ฐ€ ํ•œ ๋ช… ์ด์ƒ ์กด์žฌํ•ด์•ผ ๋ชฌ์Šคํ„ฐ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒŒ์ž„๋„ ๋งŽ๋‹ค. ์ด ๊ฒฝ์šฐ ๋ชฌ์Šคํ„ฐ์˜ ์ƒ์„ฑ ํŠธ๋ฆฌ๊ฑฐ๋Š” Cold Flow์™€ ๊ด€๋ จ์ด ์žˆ๊ฒŒ ๋˜์ง€๋งŒ, ์ƒ์„ฑ์ด ์™„๋ฃŒ๋œ ๋ชฌ์Šคํ„ฐ ๋ฐ์ดํ„ฐ๋Š” ์—ฌ์ „ํžˆ Hot Flow๋‹ค. ๋งŒ์•ฝ ์ƒ์„ฑ๋œ ๋ชฌ์Šคํ„ฐ ๋ฐ์ดํ„ฐ๊ฐ€ Cold Flow๋ผ๋ฉด, ๋ชฌ์Šคํ„ฐ์™€ ์‹ธ์šฐ๋Š” ๋‚˜์˜ ๋ชจ์Šต์ด ๋‹ค๋ฅธ ํ”Œ๋ ˆ์ด์–ด ์ž…์žฅ์—์„œ๋Š” ํ—ˆ๊ณต์— ์นผ์งˆ์„ ํ•˜๋Š” ๋ชจ์Šต์œผ๋กœ ๋ณด์ผํ…Œ๋‹ˆ ๋ง์ด๋‹ค.

 

#2 ์ฝ”๋“œ

#2-1 build.gradle.kts์˜ dependencies์— Coroutines ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”๊ฐ€

plugins {
    ...
}

...

repositories {
    ...
}

dependencies {
    ...

    // Coroutines
    implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")
}

tasks.test {
    ...
}
kotlin {
    ...
}

#2-2 ๋ฐ #2-3์—์„œ ์‚ฌ์šฉํ•  ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ๋‹ค์šด๋กœ๋“œํ•œ๋‹ค.

 

#2-2 Cold Flow์˜ ์˜ˆ์‹œ

import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.runBlocking

fun main() {
    val coldFlow = flow {
        emit("1")
        delay(1000)
        emit("2")
        delay(1000)
        emit("3")
        // ๋” ์ด์ƒ ์ˆ˜ํ–‰๋  emit()์ด ์—†์œผ๋ฏ€๋กœ ์—ฌ๊ธฐ์—์„œ flow๋Š” ์ข…๋ฃŒ๋œ๋‹ค.
    }

    runBlocking {
        coldFlow.collect { value ->
            println("(๋ฆฌ์Šค๋„ˆ 1) Collected value: $value")
        }
        println("(๋ฆฌ์Šค๋„ˆ 1) \'์ˆ˜์ง‘\' ์™„๋ฃŒ")
    }

    // ํ•œ๋ฒˆ ๋” collect()
    runBlocking {
        coldFlow.collect { value ->
            println("(๋ฆฌ์Šค๋„ˆ 2) Collected value: $value")
        }
        println("(๋ฆฌ์Šค๋„ˆ 2) \'์ˆ˜์ง‘\' ์™„๋ฃŒ")
    }
}

/* ↑ ↑ ↑ ์ถœ๋ ฅ ๊ฒฐ๊ณผ
(๋ฆฌ์Šค๋„ˆ 1) Collected value: 1
(๋ฆฌ์Šค๋„ˆ 1) Collected value: 2
(๋ฆฌ์Šค๋„ˆ 1) Collected value: 3
(๋ฆฌ์Šค๋„ˆ 1) '์ˆ˜์ง‘' ์™„๋ฃŒ
(๋ฆฌ์Šค๋„ˆ 2) Collected value: 1
(๋ฆฌ์Šค๋„ˆ 2) Collected value: 2
(๋ฆฌ์Šค๋„ˆ 2) Collected value: 3
(๋ฆฌ์Šค๋„ˆ 2) '์ˆ˜์ง‘' ์™„๋ฃŒ
*/

collect()ํ•  ๋•Œ๋งˆ๋‹ค ๋ฆฌ์Šค๋„ˆ๊ฐ€ ์ƒˆ๋กœ ๋“ฑ๋ก๋œ๋‹ค. ์ฆ‰, #1-2์˜ ์˜ˆ์‹œ์— ์žˆ๋Š” VOD ์„œ๋น„์Šค๊ฐ€ 2๋ฒˆ ์š”์ฒญ๋œ ์…ˆ์ด๋‹ค.
 

#2-3 Hot Flow์˜ ์˜ˆ์‹œ (SharedFlow)

import kotlinx.coroutines.*
import kotlinx.coroutines.flow.MutableSharedFlow

fun main() {
    /* MutableSharedFlow<Int>(replay = 3)๊ณผ ๊ฐ™์ด ๋งค๊ฐœ๋ณ€์ˆ˜๋ฅผ ๋„ฃ์–ด์ค„ ์ˆ˜๋„ ์žˆ๋‹ค.
     * replay์— ์ธ์ˆ˜๋ฅผ ์ „๋‹ฌํ•˜๋ฉด,
     * ๋ฆฌ์Šค๋„ˆ๊ฐ€ ์ƒˆ๋กœ ์ƒ๊ฒจ๋‚  ๋•Œ,
     * replay ๊ฐ’๋งŒํผ ์ตœ๊ทผ์— ๋ฐฉ์ถœ(emit)๋œ ๋ฐ์ดํ„ฐ๋ฅผ ๋‹ค์‹œ ๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.
     * ๋ง ๊ทธ๋Œ€๋กœ 'replay'๋‹ค.
     */
    val hotFlow = MutableSharedFlow<Int>()

    CoroutineScope(Dispatchers.Default).launch {
        hotFlow.collect { value ->
            println("(๋ฆฌ์Šค๋„ˆ 1) Collected value: $value")
        }
    }

    runBlocking {
        delay(1000) // emit(1) ์ „๊นŒ์ง€ ์œ„์— ์žˆ๋Š” collect() ๊ตฌ๋ฌธ์ด ๋„‰๋„‰ํžˆ ์™„๋ฃŒ๋˜๋„๋ก, delay๋ฅผ ๋„ฃ์–ด์คŒ.
        hotFlow.emit(1)
        delay(1000)
        hotFlow.emit(2)
        delay(1000)
        hotFlow.emit(3)
        delay(1000)
        /* ๋” ์ด์ƒ ์ˆ˜ํ–‰๋  emit()์ด ์—†์ง€๋งŒ,
         * Hot Flow๋Š” ์ข…๋ฃŒ๋˜์ง€ ์•Š๋Š”๋‹ค.
         * ์™œ๋ƒํ•˜๋ฉด ๋ฏธ๋ž˜์— ๋‹ค์‹œ emit()๋  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.
         */
    }

    // ํ•œ๋ฒˆ ๋” collect()
    CoroutineScope(Dispatchers.Default).launch {
        hotFlow.collect { value ->
            println("(๋ฆฌ์Šค๋„ˆ 2) Collected value: $value")
        }
    }

    runBlocking {
        delay(1000)
        hotFlow.emit(4)
        delay(1000)
        hotFlow.emit(5)
        delay(1000)
    }
}

/* ↑ ↑ ↑ ์ถœ๋ ฅ ๊ฒฐ๊ณผ
(๋ฆฌ์Šค๋„ˆ 1) Collected value: 1
(๋ฆฌ์Šค๋„ˆ 1) Collected value: 2
(๋ฆฌ์Šค๋„ˆ 1) Collected value: 3
(๋ฆฌ์Šค๋„ˆ 1) Collected value: 4
(๋ฆฌ์Šค๋„ˆ 2) Collected value: 4
(๋ฆฌ์Šค๋„ˆ 1) Collected value: 5
(๋ฆฌ์Šค๋„ˆ 2) Collected value: 5
*/

์—ฌ๊ธฐ์„œ ์‚ฌ์šฉ๋œ Flow๋Š” SharedFlow๋กœ, ์ฝ”ํ‹€๋ฆฐ์˜ ๋Œ€ํ‘œ์ ์ธ Hot Flow ์ธํ„ฐํŽ˜์ด์Šค๋‹ค. #2-2์˜ ์ฝ”๋“œ์™€๋Š” ๋‹ค๋ฅด๊ฒŒ '์ˆ˜์ง‘' ์™„๋ฃŒ๋ผ๋Š” ๋ฉ”์‹œ์ง€๋ฅผ ์ถœ๋ ฅํ•˜๋Š” ์ฝ”๋“œ๊ฐ€ ์—†๋‹ค. Hot Flow๋Š” ๋ฌดํ•œํ•œ ํƒ„์„ฑ์„ ์ง€๋‹Œ ๊ณ ๋ฌด์ค„์ฒ˜๋Ÿผ ๊ทธ ๋์ด ์—†๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ์œ„ ์ฝ”๋“œ์˜ ๋งˆ์ง€๋ง‰ ๋ถ€๋ถ„์— hotFlow.emit(6)๋ฅผ ๋„ฃ์œผ๋ฉด ๋‘ ๋ฆฌ์Šค๋„ˆ์˜ ์ˆ˜๋ช…์€ ๊ทธ๋งŒํผ ๋” ๋Š˜์–ด๋‚œ๋‹ค.
 
๊ทธ๋ ‡๊ธฐ ๋•Œ๋ฌธ์— #2-2์˜ ์ฝ”๋“œ์™€๋Š” ๋‹ค๋ฅด๊ฒŒ collect()๋ฅผ runBlocking { ... }์ด ์•„๋‹ˆ๋ผ CoroutineScope.launch { ... } ๋ธ”๋ก์— ๋„ฃ์€ ๊ฒƒ์ด๋‹ค. #2-2์— ์žˆ๋Š” runBlocking { ... } ์†์— ๋“ค์–ด์žˆ๋Š” ๊ฒƒ์€ Cold Flow์˜ collect()๋ผ์„œ ์–ธ์  ๊ฐ„ ๋๋‚˜๋ฉฐ, ๋”ฐ๋ผ์„œ runBlocking { ... } ๋ธ”๋ก์„ ํƒˆ์ถœํ•  ๊ฒƒ์„ ๊ธฐ๋Œ€ํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ ๊ทธ ์•ˆ์— Cold Flow๊ฐ€ ์•„๋‹Œ Hot Flow์˜ collect()๊ฐ€ ๋“ค์–ด์žˆ๊ฒŒ ๋œ๋‹ค๋ฉด, ๊ทธ runBlocking { ... }์€ ์˜์›ํžˆ ๋๋‚˜์ง€ ์•Š๋Š”๋‹ค.
 
์‹ค์ œ๋กœ runBlocking { ... } ๋ธ”๋ก์— Hot Flow์˜ collect()๋ฅผ ๋„ฃ์œผ๋ฉด ๊ทธ runBlocking { ... } ๋ธ”๋ก ๋’ค์— ์žˆ๋Š” ๋ชจ๋“  ์ฝ”๋“œ์— "Unreachable code"๋ผ๋Š” ๊ฒฝ๊ณ  ๋ฉ”์‹œ์ง€๊ฐ€ ๋œฌ๋‹ค.
 

#3 ์š”์•ฝ

Cold Flow๋Š” VOD์™€ ๊ฐ™๊ณ , Hot Flow๋Š” ์˜ํ™”๊ด€๊ณผ ๊ฐ™๋‹ค.