MutableStateFlow를 사용하다 보면 상태를 변경할 때 다음 두 방식 중 하나를 선택하게 된다.
state.value = newValue
state.update { currentValue ->
newValue
}
두 방식 모두 thread-safe하지만, 해결하는 문제가 다르다.
value 대입은 thread-safe
MutableStateFlow.value에 값을 대입하는 작업 자체는 원자적으로 처리된다.
state.value = Loading
여러 스레드에서 동시에 실행해도 값이 중간 상태로 깨지거나 메모리 손상이 발생하지 않는다.
다만 여러 스레드가 서로 다른 값을 대입하면 실행 순서에 따라 최종 값이 달라질 수 있다.
state.value = ValueA
state.value = ValueB
이 경우 마지막으로 반영된 값이 최종 상태가 된다. 따라서 값의 순서가 중요하다면 별도의 직렬화가 필요하다.
문제가 되는 경우는 기존 값을 읽고 변경할 때다
다음과 같이 현재 값을 읽은 뒤 변경하는 코드는 하나의 원자적 작업이 아니다.
state.value = state.value.copy(
count = state.value.count + 1,
)
두 코루틴이 동시에 실행되면 다음과 같은 일이 발생할 수 있다.
- 코루틴 A가 count == 0을 읽는다.
- 코루틴 B도 count == 0을 읽는다.
- A가 count == 1을 대입한다.
- B가 count == 1을 대입한다.
증가 연산을 두 번 수행했지만 최종 값은 1이 된다. A의 변경이 B의 오래된 값을 기준으로 덮어써졌기 때문이다.
update는 읽기와 변경을 원자적으로 처리한다
이럴 때 update를 사용한다.
state.update { currentState ->
currentState.copy(
count = currentState.count + 1,
)
}
update는 내부적으로 다음 과정을 원자적으로 수행한다.
- 현재 값을 읽는다.
- 새 값을 계산한다.
- 계산 중 기존 값이 변경되지 않았는지 확인한다.
- 변경되었다면 최신 값을 기준으로 다시 계산한다.
따라서 두 코루틴이 동시에 증가하더라도 최종 값은 2가 된다.
update가 실행 순서까지 보장하는 것은 아니다
update는 기존 값 기반 변경의 원자성을 보장하지만, 여러 업데이트의 실행 순서를 보장하지는 않는다.
state.update { ValueA }
state.update { ValueB }
최종 값은 실행 순서에 따라 달라질 수 있다. 단순히 새로운 값으로 교체하는 경우에는 value 대입과 본질적으로 같은 결과다.
실행 순서 자체를 보장해야 한다면 Mutex, 단일 이벤트 처리 루프, 직렬화된 디스패처 등의 별도 방법이 필요하다.
선택 기준
| 상황 | 적절한 방식 |
| 새로운 값을 그대로 대입 | value = newValue |
| 기존 상태를 기반으로 변경 | update { ... } |
| 증가·감소·조건부 변경 | update { ... } |
| 여러 작업의 실행 순서 보장 | 별도 동기화 필요 |
주의할 점
update의 람다는 다른 코루틴과 충돌할 경우 여러 번 실행될 수 있다.
state.update {
log("상태 변경")
it.copy(...)
}
위와 같이 람다 안에 로그 기록, 네트워크 요청, 데이터 저장 같은 부수 효과를 넣으면 해당 작업이 여러 번 실행될 수 있다. update 람다에는 상태 계산만 작성하는 것이 안전하다.
결론
value와 update의 차이는 단순한 대입의 안전성에 있지 않다. 둘 다 대입 자체는 thread-safe하다.
핵심 차이는 기존 값을 읽고 변경하는 과정의 원자성이다.
- 기존 값과 무관하게 새 값을 대입한다면 value
- 기존 값을 읽고 수정한다면 update
- 실행 순서까지 보장해야 한다면 별도의 동기화 도구
이 기준에 따라 선택하면 MutableStateFlow의 상태 변경을 더 명확하게 설계할 수 있다.
참고 자료
- https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-state-flow/
- https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-mutable-state-flow/
- https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/update.html
- https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/get-and-update.html
- https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-mutable-state-flow/value.html
'안드로이드 Android > 코틀린 Kotlin' 카테고리의 다른 글
| Android Studio 카카오 로그인 이용 중 동의, 이메일 가져오기 (0) | 2021.01.24 |
|---|---|
| 코틀린 데이터 바인딩 Kotlin data binding Unresolved reference 에러 (0) | 2021.01.07 |