kotlin

Kotlin Coroutines Deep Dive - Hot Channel vs Cold Flow

베르_최성훈 2023. 11. 1. 22:22

 

이 글은 Kotlin Coroutines : Deep Dive 책 내용을 다루고 있습니다.

 

들어가기 전에

공식문서나 책을 읽다보면 이런 얘기가 많이 나온다.

 

List, Set 과 같은 컬렉션은 Hot, Sequence 와 자바의 Stream 은 Cold 이다.

Channel 은 Hot 이지만 Flow, RxJava 스트림(observable, Single) 은 Cold 이다.

 

도대체 뭐가 뜨겁고 뭐가 차가운걸까..?

 

오늘은 데이터 소스의 두 가지 종류 Hot 데이터 스트림과 Cold 데이터 스트림의 차이를 확실히 이해해보자.

 

Hot vs Cold

Hot 데이터 스트림은 열정적이라 데이터를 소비하는 것과 무관하게 원소를 생성한다.

Cold 데이터 스트림은 게을러서 요청이 있을 때만 작업을 수행하며 아무것도 저장하지 않는다.

 

Hot 인 리스트와 Cold 인 시퀀스를 사용할 때 그 차이가 드러난다.

 

fun main() {
    val l = buildList {
        repeat(3) {
            add("User$it")
            println("리스트에 추가")
        }
    }

    val l2 = l.map {
        println("리스트 처리중 ")
        "리스트 처리된 $it"
    }

    val s = sequence {
        repeat(3) {
            yield("User$it")
            println("시퀀스에 추가")
        }
    }

    val s2 = s.map {
        println("시퀀스 처리중")
        "시퀀스 처리된 $it"
    }
}

 

 

이렇게 main 을 실행하면 결과가 어떻게 될까?

 

 

 

 

Hot 데이터 스트림의 빌더와 연산은 즉각 실행된다. 하지만 Cold 데이터 스트림은 원소가 필요할 때 까지 실행되지 않는다.

 

그 결과 Cold 데이터 스트림은 

  • 무한할 수 있다.
  • 최소한의 연산만 수행한다.
  • 값을 저장할 필요가 없어 메모리를 적게 사용한다.

Hot 데이터 스트림은

 

  • 항상 사용 가능한 상태이다.
  • 여러 번 사용되었을 때 매번 결과를 다시 계산할 필요가 없다.

 

Hot Channel vs Cold Flow

수신자가 없을 떄 데이터 생성이 중단되는 것과 요청할 때 데이터를 생성하는 것의 차이를 알아야한다.

 

Channel 

채널은 Hot 스트림이라 소비되는 것과 상관없이 곧바로 값을 생성한 뒤 가진다.

수신자가 얼마나 많은지는 신경쓰지 않으며 각 원소는 단 한 번만 받을 수 있다.

 

끝나거나 중단되거나 취소할 때 channel 의 close 를 반드시 호출하는 produce 빌더로 채널을 만든 예시를 보자.

 

 

위 함수를 실행하면 다음과 결과를 얻을 수 있다.

 

 

첫 번째 수신자가 원소를 다 소비했기 때문에 두 번째 소비자는 이미 닫혀있는 채널을 마주한다.

 

Flow

flow 는 Cold 이기 때문에 방식이 매우 달라진다.

값이 필요할 때만 생성한다.

 

그래서 이 책에서는 "flow 가 빌더가 아니며 어떤 처리도 하지 않는다. 단지 최종 연산(collect 등)이 호출될 때 어떻게 생성해야 하는지 정의한 것에 불과하다." 라고 말한다.

 

flow 빌더에는 CoroutineScope 가 필요하지 않으며 flow 빌더는 빌더를 호출한 최종 연산의 스코프에서 실행된다.

 

플로우의 각 최종 연산은 처음부터 데이터를 처리한다.

 

 

실행하면 다음과 같은 결과를 얻는다.

 

데이터를 소비하기 시작할 때 처음부터 데이터를 처리하며 최종 연산의 스코프에서 처리된다.

 

요약 

대부분의 데이터 소스는 Hot 이거나 Cold 이다.

 

Hot 은 열정적이다. 가능한 원소를 빨리 만들고 저장하며, 원소의 소비와 무관하게 생성한다. (List, Set, Channel 등)

 

Cold 는 게으르다. 최종 연산에서 값이 필요할 때 처리한다. 중간 연산자로 처리한 것들은 무엇을 해야할지만 정의한 것이다.

연산은  최소한으로 수행하며, 무한정일 수 있다. 원소의 생성과 처리는 대개 소비와 같은 과정으로 이루어진다. (Sequence, Stream, Flow, RxJava 스트림 등)