MutableStateFlow를 사용하며 값을 업데이트 하는 방식 중에는 value와 update가 있다.
이 둘의 차이를 정확히 알고 사용해야 한다. 모르고 아무 생각없이 사용 시 생길 수 있는 문제점에 대해 말하고자 한다.
data class CounterState(val count: Int = 0)
class CounterViewModel {
// 상태를 관리하는 MutableStateFlow
private val _state = MutableStateFlow(CounterState())
val state: StateFlow<CounterState> = _state
// value.copy로 상태를 업데이트
suspend fun incrementWithCopy() {
_state.value = _state.value.copy(count = _state.value.count + 1)
}
// update를 사용하여 상태를 업데이트
suspend fun incrementWithUpdate() {
_state.update { it.copy(count = it.count + 1) }
}
}
다음과 같은 CounterViewModel이 있고 카운터를 증가시키는 기능이 있다고 하자.
value를 사용하여 값을 업데이트 하는 함수와 update를 사용하여 상태를 업데이트하는 함수 두 개를 직접 실행하여 비교해보자.
@Test
fun `value_copy로 동시성 문제 발생`() = runBlocking {
val viewModel = CounterViewModel()
val expectedIncrements = 1000 // 증가 횟수
// 여러 코루틴이 동시에 상태를 증가시키는 작업 실행
coroutineScope {
repeat(expectedIncrements) {
launch(Dispatchers.Default) {
viewModel.incrementWithCopy()
}
}
}
println(viewModel.state.value.count)
// 최종 count 값이 expectedIncrements와 일치하는지 확인
assertNotEquals(expectedIncrements, viewModel.state.value.count)
}
위 테스트 코드의 내용은 count를 1000번 더하며 상태를 업데이트 하여 1000이 제대로 나오는 지를 확인하는 코드이다.
메서드 명과 같이 동시성 문제가 발생하여 1000이 아닌 값이 출력되고 test는 1000과 다른 값이므로 pass가 된다.
그렇다면 update를 사용하면 어떨까?
@Test
fun `update 사용으로 동시성 문제 해결`() = runBlocking {
val viewModel = CounterViewModel()
val expectedIncrements = 1000 // 증가 횟수
// 여러 코루틴이 동시에 상태를 증가시키는 작업 실행
coroutineScope {
repeat(expectedIncrements) {
launch(Dispatchers.Default) {
viewModel.incrementWithUpdate()
}
}
}
println(viewModel.state.value.count)
// 최종 count 값이 expectedIncrements와 정확히 일치하는지 확인
assertEquals(expectedIncrements, viewModel.state.value.count)
}
똑같이 1000번 더하여 상태를 업데이트하여 값을 확인하는 코드이다.
1000이 출력되며 테스트는 pass가 된다.
즉, value는 동시성 문제가 발생하고 update는 동시성 문제를 해결할 수 있다는 것이다.
그렇다면 둘의 차이를 내부 동작을 통해 보자
value를 타고 들어가보면 get과 set으로 게터와 세터뿐만이 존재한다.
동시성 문제를 해결하는 코드는 전혀 보이지 않는다.
반면에 update는 코드에 무언가가 있다.
compareAndSet을 한 번 보자
내부적으로 synchronized 블럭을 사용하고 있다.
즉, update는 compareAndSet을 통해 원자성을 보장하여 안전하게 여러 스레드가 동시에 값을 업데이트 할 수 있게 한다.
여러 코루틴 혹은 스레드가 MutableStateFlow의 상태를 업데이트 해야한다면 update를 사용하여 값을 읽고, 수정하고, 비교하는 과정을 한 번의 원자적 연산으로 처리하여 일관된 상태 업데이트를 보장해야 할 것이다.