(Topic) 002. 코루틴 & 플로우 스터디 복기 1

코루틴 & 플로우 스터디 복기 1

코루틴 플로우 스터디를 진행하는 과정의 일환으로 간단한 퀴즈를 진행하고 있다.

내가 출제한 것과 공통 문제에 대하여 정리해본다.

1. delay에 Paramter로 어마어마게 큰 값을 넘기면 어떤 일이 발생할까?

하나의 코루틴만 있으면 상관없겠지만 큰 지연 시간을 가진 코루틴이 계속해서 생성되면 메모리 누수를 야기한다.

2. Job, 코루틴은 계층적 구조를 갖고 있다고 한다. 프로그래밍 언어 관점에서 구조화 혹은 구조적이란 단어는 무슨 뜻인가?

프로그램이 커지고 복잡한 기능이 늘어남에 따른 유지보수 비용을 감소기키기 위해

순차/선택/반복을 조합하여 제어하는 구조를 추구하는 방식을 구조화 또는 구조적 프로그래밍이라고 한다.

주요 특징으로는 프로그램은 하나의 시작점을 반드시 가지며, 블록 단위를 이용하여 프로그램을 작성하는 것을 들 수 있다.

3. 코루틴 중 join을 절대 쓸 필요가없는 코루틴은 무엇일까?

최상위 부모 코루틴. 부모 코투린은 자식 코루틴의 실행이 완료되어야만 끝나며 이를 개발자가 명시적으로 추적하지않아도 된다.

4. Dispatcher.IO가 CPU 작업을 덜 소모하는 이유는 무엇인가?

일반적인 스케줄링 알고리즘에서 I/O 작업이 CPU Burst Time이 짧다고 가정하기 때문.

5. SupervisorJob을 써서 부모에게 예외의 전파를 막는 메서드는 무엇일까?

SupervisorImpl의 childCancelled()

6. Kotlin에서 Volatile 어노테이션이 필요한 이유는 무엇일까?

AtomiacReference 로 쓰레드 세이프하게 작성할 순 있지만, JVM과의 호환을 위해서

8. 콜드 스트림과 핫스트림의 개념 및 차이점을 설명하고, 앱 개발에서 이 둘을 사용하는 경우의 예시를 한가지씩 설명하자.

Cold Stream
생성자과 소비자 관계가 성립되는 경우에만 데이터를 발행한다.

하나의 소비자에게 값을 보내며, 스트림은 소비자가 초기화하므로 소비를 시작할때부터 데이터를 발행한다.

Hot Stream
생성자가 있을 때, 여러 소비자와의 관계가 성립할 수 있다.

데이터 발행이 시작된 이후부터 모든 소비자에게 데이터를 발행하고, 소비자가 없더라도 데이터를 발행한다.

9. combine과 zip연산자의 차이점을 설명하고, 앱개발에서 이 연산자들을 사용하는 경우의 예시를 한가지씩 설명하자.

zip 연산자와 combine 연산자는 모두 다중 플로우를 합성하는 연산자이다.

zip은 두 개의 플로우에서 번갈아가면서 데이터를 수집한 뒤 새로운 데이터로 반환하고

combine은 두 개의 플로우의 데이터를 수집하긴 하지만, 한 쪽만 갱신되어도 새로운 데이터를 만들어 반환한다.

실제 애플리케이션 개발시

zip은 반드시 Map으로 구조화해야하는 데이터 등을 처리할 때

combine 연산자는 거대한 검색 결과 리스트 중, 사용자가 선택한 다중 필터를 처리할 때 유용하다.

10. 어떠한 동영상을 디코딩 한 후 리사이징 하여 다시 인코딩하려고 하는데, 이때 flow를 사용한다고 가정하면 어떤 부분을 주의해야하고, 어떤방식으로 앱을 구현하면 좋을지 생각해보자.

  • 세부조건
    • frame rate는 동일하다. 단 하나의 프레임도 누락되서는 안된다.
    • 디코딩은 빠르며, 리사이징 및 인코딩은 상대적으로 시간이 더 소요된다.
    • 작업 진행율을 ProgressBar로 표현해야 한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
// 1. 최초 모든 Frame의 Bitmap이 list 형태로 주어진다고 가정한다.
val input = listOf<Bitmap()

// 2. 디코딩은 빠르다
fun decoding(input: List<Bitmap>) = flow {
for (bitmap in input) {
// 2-1. 디코딩은 빠르다가 전제이므로 별도의 delay를 부여하지않는다.
emit(bitmap)
}
}

// 3. 디코딩된 Bitmap 리스트는 decoded에 저장된다.
val decoded = mutableListOf<Bitmap>()
decoding(input).collect { output ->
decoded.add(output)
}

// 4. 리사이징은 상대적으로 시간이 더 소요된다.
val resizing(input: List<Bitmap>) = flow {
delay(200L) // 0.2초 지연
emit(input * alpha)
}

// 5. 리사이징 결과는 resized에 저장
val resized = mutableListOf<Bitmap>()
resizing(decoded)
.buffer() // 5-1. transform으로 갈음하면 필요없는 버퍼가 된다. 즉 오버엔지니어링
.collect { output ->
resized.add(output)
}

// 6. 인코딩은 상대적으로 시간이 더 소요된다.
val encoding(input: List<Bitmap>) = flow {
delay(500L) // 0.5초 지연
emit(input * alpha)
}

// 7. 인코딩된 Bitmap 리스트는 encoded에 저장된다.
val encoded = mutableListOf<Bitmap>()
encoding(resized)
.buffer() // 7-1. 실질적으로 buffer는 가장 큰 병목이 생기는 지점에 하나만 필요하다.
.collect { output ->
encoded.add(output)
}

11. gps로 지도 상 나의 위치를 실시간으로 트래킹하는 앱을 구현한다고 가정하자. 다음의 세부조건을 고려할 떄 어떤식으로 Flow를 사용하면 좋을지 생각해보자.

  • 세부 조건
    • gps를 통해 위도,경도 좌표값을 1초에 수십회 얻을 수 있다.
    • 특정 좌표에 맞춰 지도를 렌더링하는데는 1초미만의 시간이 소요된다.
    • 가끔 gps에 노이즈로 인해 전혀 유효하지 않은 좌표가 반환되는데 이 때 이 값을 사용하지 않아야 한다.(예를 들어 이전 좌표가 서울이였는데 그 다음 값이 뉴욕인 경우)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 1. GPS 좌표를 반환하는 가상의 함수 정의
fun getLocation(): Location = LocationManagver.location

// 2. 1초마다 GPS 좌표를 얻어온다.
fun updateLocation() = flow {
while (true) { emit(getLocation()) }
}

fun renderingMap(location: Location) {
// Redering a map.
}

// 3. Flow 호출
updateLocation()
.onEach { delay(1000L )} // 3-1. 1초마다 flow에서 값을 꺼낸다.
.filterNot {
(it.longitude + it.latitude) !in 157 .. 175 // 3-2. 국내 범위가 아니면 노이즈로 판정한다.
.collect {
renderingMap(it)
}

References