본문 바로가기

안드로이드 Android/코틀린 Kotlin

MutableStateFlow의 value와 update 차이

MutableStateFlow를 사용하다 보면 상태를 변경할 때 다음 두 방식 중 하나를 선택하게 된다.

state.value = newValue
state.update { currentValue ->
    newValue
}

 

두 방식 모두 thread-safe하지만, 해결하는 문제가 다르다.

 

value 대입은 thread-safe

MutableStateFlow.value에 값을 대입하는 작업 자체는 원자적으로 처리된다.

state.value = Loading

 

여러 스레드에서 동시에 실행해도 값이 중간 상태로 깨지거나 메모리 손상이 발생하지 않는다.

(참고: https://kotlinlang.org/api/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.flow/-mutable-state-flow/value.html)

다만 여러 스레드가 서로 다른 값을 대입하면 실행 순서에 따라 최종 값이 달라질 수 있다.

state.value = ValueA
state.value = ValueB

이 경우 마지막으로 반영된 값이 최종 상태가 된다. 따라서 값의 순서가 중요하다면 별도의 직렬화가 필요하다.

 

문제가 되는 경우는 기존 값을 읽고 변경할 때다

다음과 같이 현재 값을 읽은 뒤 변경하는 코드는 하나의 원자적 작업이 아니다.

state.value = state.value.copy(
    count = state.value.count + 1,
)

두 코루틴이 동시에 실행되면 다음과 같은 일이 발생할 수 있다.

  1. 코루틴 A가 count == 0을 읽는다.
  2. 코루틴 B도 count == 0을 읽는다.
  3. A가 count == 1을 대입한다.
  4. B가 count == 1을 대입한다.

증가 연산을 두 번 수행했지만 최종 값은 1이 된다. A의 변경이 B의 오래된 값을 기준으로 덮어써졌기 때문이다.

 

update는 읽기와 변경을 원자적으로 처리한다

이럴 때 update를 사용한다.

state.update { currentState ->
    currentState.copy(
        count = currentState.count + 1,
    )
}

update는 내부적으로 다음 과정을 원자적으로 수행한다.

  1. 현재 값을 읽는다.
  2. 새 값을 계산한다.
  3. 계산 중 기존 값이 변경되지 않았는지 확인한다.
  4. 변경되었다면 최신 값을 기준으로 다시 계산한다.

따라서 두 코루틴이 동시에 증가하더라도 최종 값은 2가 된다.

 

update가 실행 순서까지 보장하는 것은 아니다

update는 기존 값 기반 변경의 원자성을 보장하지만, 여러 업데이트의 실행 순서를 보장하지는 않는다.

state.update { ValueA }
state.update { ValueB }

 

최종 값은 실행 순서에 따라 달라질 수 있다. 단순히 새로운 값으로 교체하는 경우에는 value 대입과 본질적으로 같은 결과다.

실행 순서 자체를 보장해야 한다면 Mutex, 단일 이벤트 처리 루프, 직렬화된 디스패처 등의 별도 방법이 필요하다.

 

선택 기준

상황 적절한 방식
새로운 값을 그대로 대입 value = newValue
기존 상태를 기반으로 변경 update { ... }
증가·감소·조건부 변경 update { ... }
여러 작업의 실행 순서 보장 별도 동기화 필요

 

주의할 점

update의 람다는 다른 코루틴과 충돌할 경우 여러 번 실행될 수 있다.

state.update {
    log("상태 변경")
    it.copy(...)
}

 

위와 같이 람다 안에 로그 기록, 네트워크 요청, 데이터 저장 같은 부수 효과를 넣으면 해당 작업이 여러 번 실행될 수 있다. update 람다에는 상태 계산만 작성하는 것이 안전하다.

 

결론

valueupdate의 차이는 단순한 대입의 안전성에 있지 않다. 둘 다 대입 자체는 thread-safe하다.

핵심 차이는 기존 값을 읽고 변경하는 과정의 원자성이다.

  • 기존 값과 무관하게 새 값을 대입한다면 value
  • 기존 값을 읽고 수정한다면 update
  • 실행 순서까지 보장해야 한다면 별도의 동기화 도구

이 기준에 따라 선택하면 MutableStateFlow의 상태 변경을 더 명확하게 설계할 수 있다.

 

참고 자료