728x90
코틀린 코루틴을 요약한 내용입니다.
- 채널은 값을 핫(hot) 스트림으로 가지지만, 콜드(cold) 스트림이 필요할 때가 있습니다.
- 우리가 사용하는 대부분의 데이터 소스는 두 가지 종류로 구분할 수 있음
- 컬렉션은 핫, Channel은 핫
- Sequence와 자바의 Stream은 콜드, Flow와 RxJava 스트림은 콜드
핫 vs 콜드
- 핫 데이터 스트림의 빌더와 연산은 즉각 실행
- 콜드 데이터 스트림에서는 원소가 필요할 때 까지 실행되지 않음
- fun main() { val l = builList { repeat(3) { add("User$id") println("L: Added User") } } val l2 = l.map { println("L: Processing") "Processed $it" } val s = sequence { repeat(3) { yield("User$it") println("S: Added User") } } val s2 = s.map { println("S: Processing") "Processed $it" } }
- 콜드 데이터 스트림
- 무한할 수 있다
- 최소한의 연산만 수행
- 메모리를 적게 사용 (중간에 생성되는 값을 보관할 필요가 없기 때문에)
- Sequence는 원소를 지연 처리하기 때문에 더 적은 연산을 수행
- 중간 연산은 이전에 만든 시퀀스에 새로운 연산을 첨가
- 최종 연산이 모든 작업을 실행
- 시퀀스의 처리 방식은 모든 중간 과정을 계산하고 모든 데이터 처리가 완료된 컬렉션을 반환하는 리스트의 처리 방식과는 다름
- 리스트의 경우 원소의 처리 순서가 달라지며, 컬렉션 처리 과정에서 좀 더 많은 메모리를 필요로 하고, 더 많은 연산을 수행
- 리스트는 원소의 컬렉션
- 시퀀스는 원소를 어떻게 계산할지 정의한 것
fun m(i: Int): Int { print("m$i ") return i * i } fun f(i: Int): Boolean { print("f$i ") return i >= 10 } fun main() { listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .map { m(it) } .find { f(it) } .let { print(it) } println() sequenceOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .map { m(it) } .find { f(it) } .let { print(it) } }
- 핫 데이터 스트림
- 항상 사용 가능한 상태 (각 연산이 최종 연산이 될 수 있음)
- 여러 번 사용되었을 때 매번 결과를 다시 계산할 필요가 없음
fun m(i: Int): Int { print("m$i ") return i * i } fun main() { val l = listOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .map { m(it) } println(l) println(l.find { it > 10 }) println(l.find { it > 10 }) println(l.find { it > 10 }) val s sequenceOf(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) .map { m(it) } println(s.toList()) println(s.find { it > 10 }) println(s.find { it > 10 }) println(s.find { it > 10 }) }
핫 채널, 콜드 플로우
- 플로우를 생성하는 가장 일반적인 방법은 produce 함수와 비슷한 형태의 빌더를 사용
- 빌더가 바로 flow
val channel = produce { while (true) { val x = computeNextValue() send(x) } } val flow = flow { while (true) { val x = computeNextValue() emit(x) } }
- 두 빌더는 개념적으로는 동일, 채널과 플로우의 방식이 아주 다름
- 채널은 핫이라 값을 곧바로 계산
- 별도의 코루틴에서 계산을 수행
- produce는 CoroutineScope의 확장 함수로 정의되어 있는 코루틴 빌더가 되어야 함
- 소비되는 것과 상관없이 값을 생성한 뒤에 가지게 됨
- 수신자가 얼마나 많은지 시경 쓰지 않음
- 각 원소는 단 한 번만 받을 수 있기 때문에, 첫 번째 수신자가 모든 원소를 소비하고 나면 두 번째 소비자는 채널이 비어 있으며 닫혀 있다는 걸 발견
private fun CoroutineScope.makeChannel() = produce { println("Channel stared") for (i in 1..3) { delay(1000) send(i) } } suspend fun main() = coroutineScope { val channel = makeChannel() delay(1000) println("Calling channel...") for (value in channel) { println(value) } println("Calling again...") for (value in channel) { println(value) } }
- 플로우는 콜드 데이터 소스이기 때문에 값이 필요할 때만 생성
- flow는 빌더가 아니면 어떤 처리도 하지 않음
- 최종 연산이 호출될 때 원소가 어떻게 생성되어야 하는지 정의한 것에 불과
- flow 빌더는 CoroutineScope가 필요하지 않음
private fun makeFlow() = flow { println("Flow started") for (i in 1..3) { delay(1000) emit(i) } } suspend fun main() = coroutineScope { val flow = makeFlow() delay(1000) println("Calling flow...") flow.collect { value -> println(value) } println("Consuming again...") flow.collect { value -> println(value) } }
요약
- 대부분의 데이터 소스는 핫이거나 콜드
- 핫 데이터
- 가능한 빨리 원소를 만들고 저장
- 원소가 소비되는 것과 무관하게 생성
- 컬렉션(List, Set)과 Channel이 있음
- 콜드 데이터
- 최종 연산에서 값이 필요할 때가 되어서야 처리
- 중간 과정의 모든 함수는 무엇을 해야 할지만 정의
- 연산은 최소한으로 수행, 무한정일 수 있음
- Sequence, 자바의 Steram, Flow, RxJava 스트림
728x90
'코틀린 스터디' 카테고리의 다른 글
플로우의 실제 구현 (0) | 2024.02.02 |
---|---|
플로우란 무엇인가? (1) | 2024.01.04 |
셀렉트 (0) | 2024.01.04 |
채널 (0) | 2023.12.04 |
코루틴 스코프 만들기 (1) | 2023.11.24 |