Hot Stream & Cold Stream

Hot Stream & Cold Stream

  • 채널은 값을 핫 스트림으로 가지며, 콜드 스트림이 필요할 때가 있습니다.
  • 핫 스트림 : List, Set과 같은 컬렉션, Channel 등
  • 콜드 스트림 : Sequence, Stream, Flow, RxJava(Observable, Single) 등

핫 스트림

  • 핫 데이터 스트림은 데이터를 소비하는 것과 무관하게 원소를 생성합니다.

콜드 스트림

  • 콜드 데이터 스트림은 요청이 있을 때만 작업을 수행하며, 아무것도 저장하지 않습니다.

스트림 비교

fun main() {
    // 핫 스트림
    val l = buildList {
        repeat(3){
            add("User$it")
            println("Added User in list")
        }
    }

    val l2 = l.map {
        println("Processing in list")
        "Processed $it"
    }

    // 콜드 스트림
    val s = sequence {
        repeat(3) {
            // yield:시퀀스나 제너레이터에서 값을 반환하고, 함수의 실행을 중단한 후 다시 재개
            yield("User$it")
            println("Added User in sequence")
        }
    }

    val s2 = s.map {
        println("Processing in sequence")
    }
}
  • Sequence와 같은 콜드 데이터 스트림은 원소가 필요할 때까지 실행되지 않습니다.
  • 콜드 데이터 스트림은
    • 무한할 수 있습니다.
    • 최소한의 연산만 수행합니다.
    • 중간 값을 보관할 필요가 없기 때문에 메모리를 적게 사용합니다.

Hot Channel & Cold Flow

  • 코루틴에서 사용되는 대표적 스트림을 비교하기 위해서 코드로 구현하였습니다.

Channel 생성

// 채널은 코루틴 스코프 내부에서 실행되어야 합니다.
@OptIn(ExperimentalCoroutinesApi::class)
private fun CoroutineScope.makeChannel() = produce {
    println("Channel make")
    for (i in 1..2){
        delay(1000)
        send(i)
    }
}
  • 채널을 생성하는 확장 함수를 작성합니다.
  • 채널은 코루틴 스코프 내부에서 생성되어야 합니다.

Flow 생성

// 플로우는 생성시 코루틴을 필요로 하지 않습니다.
// flow 빌더는 빌더를 호출한 최종 연산의 스코프에서 실행됩니다.
private fun makeflow() = flow {
    println("Flow make")
    for(i in 1..2){
        delay(1000)
        emit(i)
    }
}
  • 플로우를 생성하는 확장 함수를 작성합니다.
  • flow는 최종 연산이 호출될 때 원소가 어떻게 생성되어야 하는지만 정의하기 때문에 생성 시 스코프를 사용하지 않습니다.

consumer 로직 비교

suspend fun main() = coroutineScope {
    val channel = makeChannel()
    val flow = makeflow()

    delay(1000)
    println("Start channel")
    // 바로 값을 생성 합니다. (Start 이전에 수행)
    channel.consumeEach {
        println("channel consume : $it")
    }
    println("ReStart channel")
    // 첫 소비자가 수신했기 때문에, 채널이 비어있음
    channel.consumeEach {
        println("channel reConsume : $it")
    }

    println("-----------------------")
    delay(1000)
    println("Start flow")
    // 콜드 데이테 소스로, 값이 필요할 때만 생성
    flow.collect {
        println("flow consume : $it")
    }
    println("ReStart flow")
    // 처음부터 다시 데이터를 처리하기 때문에, 또다시 수집합니다.
    flow.collect {
        println("flow reConsume : $it")
    }
}

  • 각 로직을 비교하기 위해 consumer 로직을 구현하였습니다.
  • channel은 값을 바로 생성하며, 이미 수집한 데이터를 다시 수집하지 않습니다.
  • flow는 값을 사용 시 생성하며, collect를 통해 데이터를 연속으로 수집합니다.

정리

  • 핫 데이터 소스는 열정적이며, 가능한 빨리 원소를 만들어 저장합니다.
    • 소비되는 것과 무관하게 생성
  • 콜드 데이터 소스는 게으르며, 최종 연산에서 값이 필요할 때 처리합니다.
    • flow는 무엇을 할지만 명시하며, 수집을 통해 데이터가 완성됩니다.
    • 따라서 일반적으로 원소를 저장하지 않습니다.