๐ฌ Android ๊ฐ๋ฐ์์ ์ํ ๊ด๋ฆฌ๋ ํต์ฌ ์์์
๋๋ค.
ํนํ MutableStateFlow๋ Kotlin Coroutines์ ํจ๊ป ์ฐ์ด๋ ๊ฐ๋ ฅํ ์ํ ๊ด๋ฆฌ ๋๊ตฌ๋ก,
๋ง์ ๊ฐ๋ฐ์๋ค์ด ์ ํธํ๋ ํจํด์
๋๋ค.
๐ MutableStateFlow์ ๊ฐ์ ๋ณ๊ฒฝํ ๋๋
value update๐ ๊ฐ ๋ฐฉ๋ฒ์ ๋์ ์๋ฆฌ์ ์ฐจ์ด๋ฅผ ์ดํดํ๋ฉด
๋ ์์ ํ๊ณ ํจ์จ์ ์ธ ์ฝ๋๋ฅผ ์์ฑํ ์ ์์ต๋๋ค.
๐ ์ ์: ๋ณ๊ฒฝ ๊ฐ๋ฅํ ์ํ๋ฅผ ๊ฐ์ง๋ฉฐ, ์ํ ๋ณํ๋ฅผ ๊ด์ฐฐํ ์ ์๋ Hot Stream
MutableStateFlow๋ StateFlow์ ๋ณ๊ฒฝ ๊ฐ๋ฅํ ๊ตฌํ์ฒด๋ก, ๋ค์๊ณผ ๊ฐ์ ํน์ง์ ๊ฐ์ง๋๋ค:
MutableStateFlow์ ๊ฐ์ ๋ณ๊ฒฝํ๋ ๋ฐฉ๋ฒ์ ํฌ๊ฒ ๋ ๊ฐ์ง์ ๋๋ค:
val mutableStateFlow = MutableStateFlow(0)
// ๋ฐฉ๋ฒ 1: value ์ง์ ํ ๋น
mutableStateFlow.value = 10
// ๋ฐฉ๋ฒ 2: update ํจ์ ์ฌ์ฉ
mutableStateFlow.update { currentValue ->
currentValue + 1
}
๊ฐ๊ฐ์ ๋์ ๋ฐฉ์๊ณผ ํน์ง์ ์์ธํ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
class CounterViewModel : ViewModel() {
private val _counter = MutableStateFlow(0)
val counter: StateFlow<Int> = _counter.asStateFlow()
fun incrementDirectly() {
_counter.value = _counter.value + 1
}
fun setCounter(newValue: Int) {
_counter.value = newValue
}
}
value ์์ฑ์ ์ฌ์ฉํ๋ฉด ๋ค์๊ณผ ๊ฐ์ด ๋์ํฉ๋๋ค:
// ๋ด๋ถ์ ์ผ๋ก ์ด๋ ๊ฒ ๋์
_counter.value = 5 // ์ฆ์ 5๋ก ์ค์ ๋จ
println(_counter.value) // 5 ์ถ๋ ฅ
๐ก๏ธ Thread-Safety ๋ณด์ ์ค๋ช
MutableStateFlow.value์ ์ฝ๊ธฐ/์ฐ๊ธฐ ์์ฒด๋ ์์์ ์ด์ง๋ง,
value = value + 1์ฒ๋ผ ์ฝ๊ธฐ โ ๊ณ์ฐ โ ์ฐ๊ธฐ๋ก ์ด์ด์ง๋ ๋ณตํฉ ์ฐ์ฐ์ ์์์ ์ด์ง ์์ต๋๋ค.
๋ฐ๋ผ์ ๋ฉํฐ์ค๋ ๋ ํ๊ฒฝ์์ ๋์์ ๊ฐ์ ๊ฐ์ ์ฝ์ด ๋ฎ์ด์ฐ๋ Race Condition์ด ๋ฐ์ํ ์ ์์ต๋๋ค.
์ด๋ฐ ๊ฒฝ์ฐ์๋update { it + 1 }์ฒ๋ผ ์์์ CAS ๊ธฐ๋ฐ ์ฐ์ฐ์ ์ฌ์ฉํ์ธ์.
class ProblematicCounter : ViewModel() {
private val _counter = MutableStateFlow(0)
fun incrementTwice() {
viewModelScope.launch {
_counter.value = _counter.value + 1
}
viewModelScope.launch {
_counter.value = _counter.value + 1
}
}
}
โ ๏ธ ์ฃผ์: Race Condition ๊ฐ๋ฅ์ฑ
๋ฉํฐ์ค๋ ๋ ํ๊ฒฝ์์ ๋ ์ฝ๋ฃจํด์ด ๋์์ ์คํ๋๋ฉด
๊ฐ์ ์ด๊ธฐ๊ฐ์ ์ฝ๊ณ ๋์์ ์ฆ๊ฐ์ํฌ ์ ์์ต๋๋ค.โ ์์ ๊ฒฐ๊ณผ:
+2์ฆ๊ฐ
โ ์ค์ ๊ฒฐ๊ณผ:+1๋ง ์ฆ๊ฐํ ์ ์์
data class UserState(
val name: String = "",
val age: Int = 0,
val isActive: Boolean = false
)
class UserViewModel : ViewModel() {
private val _userState = MutableStateFlow(UserState())
fun updateUserUnsafely() {
val currentState = _userState.value
_userState.value = currentState.copy(
name = "์ ์ด๋ฆ",
age = currentState.age + 1
)
}
}
โ ๏ธ ์ฃผ์: ๋ฐ์ดํฐ ์ผ๊ด์ฑ ๋ฌธ์
๋ณตํฉ ๊ฐ์ฒด๋ฅผ ์์ ํ ๋
currentState๋ฅผ ์ฝ๋ ์์ ๊ณผ ์ ๊ฐ์ ์ค์ ํ๋ ์์ ์ฌ์ด์
๋ค๋ฅธ ์ค๋ ๋๊ฐ ๊ฐ์ ๋ณ๊ฒฝํ ์ ์์ด
๋ฐ์ดํฐ ์ผ๊ด์ฑ ๋ฌธ์ ๊ฐ ๋ฐ์ํ ์ ์์ต๋๋ค.
@Test
fun testRaceCondition() = runTest {
val counter = MutableStateFlow(0)
val jobs = List(2) {
launch {
repeat(1000) {
counter.value = counter.value + 1
}
}
}
jobs.joinAll()
println("์ต์ข
๊ฐ: ${counter.value}")
}
โ ๏ธ ์ฃผ์: Race Condition ๊ฒฐ๊ณผ
์ด ํ ์คํธ๋ฅผ ์คํํ๋ฉด
์์๊ฐ2000๋ณด๋ค ์์ ๊ฐ์ด ๋์ฌ ๊ฐ๋ฅ์ฑ์ด ๋์ต๋๋ค.
class SafeCounterViewModel : ViewModel() {
private val _counter = MutableStateFlow(0)
val counter: StateFlow<Int> = _counter.asStateFlow()
fun incrementSafely() {
_counter.update { currentValue ->
currentValue + 1
}
}
fun multiplyByTwo() {
_counter.update { it * 2 }
}
}
public inline fun <T> MutableStateFlow<T>.update(function: (T) -> T) {
while (true) {
val prevValue = value
val nextValue = function(prevValue)
if (compareAndSet(prevValue, nextValue)) {
return
}
}
}
update ํจ์๋ Compare-And-Swap(CAS) ๋ฉ์ปค๋์ฆ์ ์ฌ์ฉํ์ฌ ๋ค์๊ณผ ๊ฐ์ด ๋์ํฉ๋๋ค:
fun updateExample() {
_counter.update { currentValue ->
currentValue + 1
}
}
// ๋ด๋ถ ๋์:
// 1. while (true) ๋ฃจํ ์์
// 2. val prevValue = value // ํ์ฌ ๊ฐ ์ค๋
์ท
// 3. val nextValue = function(prevValue) // ๋ณํ ํจ์ ์คํ
// 4. if (compareAndSet(prevValue, nextValue)) // CAS ์ฐ์ฐ
// - ์ฑ๊ณต ์: return (๋ฃจํ ์ข
๋ฃ)
// - ์คํจ ์: 1๋จ๊ณ๋ก ๋์๊ฐ์ ์ฌ์๋
// compareAndSet์ ๊ฐ๋
์ ๋์
fun compareAndSet(expected: T, newValue: T): Boolean {
// ์์์ ์ผ๋ก(atomically) ๋ค์์ ์ํ:
if (currentValue == expected) {
currentValue = newValue
return true // ์ฑ๊ณต
} else {
return false // ์คํจ (๋ค๋ฅธ ์ค๋ ๋๊ฐ ๊ฐ์ ๋ณ๊ฒฝํจ)
}
}
// ๋ฉํฐ์ค๋ ๋ ํ๊ฒฝ์์์ update ๋์ ์๋ฎฌ๋ ์ด์
Thread A: _counter.update { it + 1 }
Thread B: _counter.update { it * 2 }
์ด๊ธฐ๊ฐ: 10
๐งต Thread A โ ๐ฅ prevValue = 10
๐ง nextValue = 10 + 1 = 11
๐ compareAndSet(10, 11) ์๋...
๐งต Thread B (๋์์ ์คํ) โ ๐ฅ prevValue = 10
๐ง nextValue = 10 * 2 = 20
โ
compareAndSet(10, 20) ์ฑ๊ณต โ ๊ฐ์ด 20์ผ๋ก ๋ณ๊ฒฝ
๐งต Thread A (๊ณ์) โ โ compareAndSet(10, 11) ์คํจ (ํ์ฌ๊ฐ์ด 20)
โป๏ธ while ๋ฃจํ๋ก ์ฌ์๋
๐ฅ prevValue = 20
๐ง nextValue = 20 + 1 = 21
โ
compareAndSet(20, 21) ์ฑ๊ณต
๐ ์ต์ข
๊ฒฐ๊ณผ: 21 (๋ชจ๋ ์ฐ์ฐ์ด ์์ ํ๊ฒ ์ ์ฉ๋จ)
class ThreadSafeCounter : ViewModel() {
private val _counter = MutableStateFlow(0)
fun incrementTwiceSafely() {
viewModelScope.launch {
_counter.update { it + 1 }
}
viewModelScope.launch {
_counter.update { it + 1 }
}
}
}
โ ์์์ ์คํ ๋ณด์ฅ
์ ์ฝ๋์์ ๋ ๊ฐ์ ์ฝ๋ฃจํด์ด ๋์์ ์คํ๋์ด๋
๊ฐupdateํธ์ถ์ ์์์ (atomic) ์ผ๋ก ์คํ๋ฉ๋๋ค.๐ง CAS ๋ฉ์ปค๋์ฆ์ด ๋์์ฑ ๋ฌธ์ ๋ฅผ ์๋์ผ๋ก ํด๊ฒฐํ์ฌ
์ต์ข ์ ์ผ๋ก ์ ํํ+2์ฆ๊ฐํจ์ ๋ณด์ฅํฉ๋๋ค.
class SafeUserViewModel : ViewModel() {
private val _userState = MutableStateFlow(UserState())
fun updateUserSafely() {
_userState.update { currentState ->
currentState.copy(
name = "์ ์ด๋ฆ",
age = currentState.age + 1
)
}
}
}
๐ ํญ์ ์ต์ ์ํ ๊ธฐ๋ฐ ์ฐ์ฐ
updateํจ์๋ ๋๋ค ๋ด๋ถ์์ ํญ์ ์ต์ ์ํ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฐ์ฐ์ ์ํํฉ๋๋ค.โก ๋ง์ฝ ๋ค๋ฅธ ์ค๋ ๋๊ฐ ์ค๊ฐ์ ์ํ๋ฅผ ๋ณ๊ฒฝํ๋ค๋ฉด,
CAS๊ฐ ์คํจํ๊ณ ์๋์ผ๋ก ์ฌ์๋ํ์ฌ
์๋ก์ด ์ต์ ์ํ๋ฅผ ๋ค์ ๊ฐ์ ธ์ ์์ ํ ์ ๋ฐ์ดํธ๋ฅผ ๋ณด์ฅํฉ๋๋ค.
fun automaticRetryExample() {
_counter.update { value ->
value + 1
}
}
๋ค๋ฅธ ์ค๋ ๋๊ฐ ๊ฐ์ ๋ณ๊ฒฝํด๋
updateํจ์๊ฐ ์๋์ผ๋ก ์ฌ์๋ํ์ฌ ํญ์ ์ต์ ๊ฐ์ ๊ธฐ๋ฐ์ผ๋ก ์ฐ์ฐ์ ์ํํฉ๋๋ค. ๊ฐ๋ฐ์๋ ๋ณต์กํ ๋์์ฑ ์ฒ๋ฆฌ ๋ก์ง์ ์ง์ ๊ตฌํํ ํ์๊ฐ ์์ต๋๋ค.
fun performanceAnalysis() {
val counter = MutableStateFlow(0)
// Case 1: ๊ฒฝํฉ์ด ์๋ ๋จ์ผ ์ค๋ ๋ ํ๊ฒฝ
counter.update { it + 1 }
// โ CAS ํ ๋ฒ์ ์ฑ๊ณต, ์ต์ ์ค๋ฒํค๋
// Case 2: ๋์ ๊ฒฝํฉ์ด ์๋ ๋ฉํฐ์ค๋ ๋ ํ๊ฒฝ
repeat(1000) {
launch {
counter.update { it + 1 } // ์ฌ๋ฌ ๋ฒ ์ฌ์๋ ๊ฐ๋ฅ
}
}
}
| ๐ ํญ๋ชฉ | โ๏ธ value ๋ฐฉ์ | ๐ update ๋ฐฉ์ |
|---|---|---|
| โ๏ธ ๋จ์ผ ์ค๋ ๋ ์ฑ๋ฅ | ์ฆ์ ์คํ | CAS 1ํ ์ํ |
| ๐ค ๋ฉํฐ์ค๋ ๋ ์ฑ๋ฅ | โ Race Condition ๋ฐ์ ๊ฐ๋ฅ | CAS + ์๋ ์ฌ์๋ |
| ๐ ๋ฉ๋ชจ๋ฆฌ ํ ๋น | ์ถ๊ฐ ํ ๋น ์์ | ๋๋ค ๊ฐ์ฒด ์์ฑ |
| ๐ฅ CPU ์ฌ์ฉ๋ | ์ต์ | ์ฌ์๋ ์ ์ฆ๊ฐ |
| ๐ ๊ตฌ๋ถ | โ๏ธ value ๋ฐฉ์ | ๐ update ๋ฐฉ์ |
|---|---|---|
| ๐ก ์ฌ์ฉ ํธ์์ฑ | ๊ฐ๋จํ ์ง์ ํ ๋น | ํจ์ํ ์ ๊ทผ |
| ๐ก Thread Safety | โ Race Condition ์ํ | โ CAS๋ก ์์ ๋ณด์ฅ |
| โ๏ธ ์ฑ๋ฅ | ์ฆ์ ์คํ | CAS ์ค๋ฒํค๋ ์ฝ๊ฐ ์์ |
| ๐ ๋ณตํฉ ๊ฐ์ฒด ์์ | โ ๏ธ ๋ถ์์ | โ ์์ ํ ์์์ ์์ |
| ๐งฉ ์กฐ๊ฑด๋ถ ๋ก์ง | โ ์ธ๋ถ์์ ์ฒ๋ฆฌ ํ์ | โ ํจ์ ๋ด๋ถ์์ ์ฒ๋ฆฌ |
| ๐ ์ฝ๋ ๊ฐ๋ ์ฑ | ์ง๊ด์ | ํจ์ํ ์คํ์ผ |
| โป๏ธ ์ฌ์๋ ์ฒ๋ฆฌ | โ ์๋ ๊ตฌํ ํ์ | โ ์๋ ์ฒ๋ฆฌ |
class SimpleViewModel : ViewModel() {
private val _isLoading = MutableStateFlow(false)
fun setLoading(loading: Boolean) {
_isLoading.value = loading
}
fun resetCounter() {
_counter.value = 0
}
}
๐ก value ๋ฐฉ์์ด ์ ํฉํ ๊ฒฝ์ฐ
๋จ์ํ boolean ํ ๊ธ์ด๋ ์ด๊ธฐํ์ฒ๋ผ
๋ช ํํ ๊ฐ ์ค์ ์ด ํ์ํ๊ณ ๋จ์ผ ์ค๋ ๋์์ ์ฌ์ฉ๋๋ ๊ฒฝ์ฐ์๋
value๋ฐฉ์์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ ํฉํฉ๋๋ค.
class RecommendedViewModel : ViewModel() {
private val _uiState = MutableStateFlow(UiState())
fun updateUiState() {
_uiState.update { state ->
state.copy(
isLoading = false,
data = newData,
errorMessage = null
)
}
}
fun incrementCounter() {
_counter.update { it + 1 }
}
fun incrementIfLessThan100() {
_counter.update { current ->
if (current < 100) current + 1 else current
}
}
}
๐ก update ๋ฐฉ์์ด ํ์์ ์ธ ๊ฒฝ์ฐ
๋ณตํฉ ๊ฐ์ฒด ์์ , ๋ฉํฐ์ค๋ ๋ ํ๊ฒฝ์์์ ์นด์ดํฐ ์ฆ๊ฐ,
์กฐ๊ฑด๋ถ ์ ๋ฐ์ดํธ ๋ฑ์ ๊ฒฝ์ฐ์๋
๋ฐ๋์update๋ฐฉ์์ ์ฌ์ฉํด์ผ ํฉ๋๋ค.
data class ShoppingCartState(
val items: List<CartItem> = emptyList(),
val totalPrice: Double = 0.0,
val isLoading: Boolean = false,
val errorMessage: String? = null
)
class ShoppingCartViewModel : ViewModel() {
private val _cartState = MutableStateFlow(ShoppingCartState())
val cartState: StateFlow<ShoppingCartState> = _cartState.asStateFlow()
fun addItem(item: CartItem) {
_cartState.update { state ->
val newItems = state.items + item
state.copy(
items = newItems,
totalPrice = calculateTotal(newItems),
errorMessage = null
)
}
}
fun removeItem(itemId: String) {
_cartState.update { state ->
val newItems = state.items.filter { it.id != itemId }
state.copy(
items = newItems,
totalPrice = calculateTotal(newItems)
)
}
}
fun updateQuantity(itemId: String, newQuantity: Int) {
_cartState.update { state ->
val newItems = state.items.map { item ->
if (item.id == itemId) {
item.copy(quantity = newQuantity)
} else {
item
}
}
state.copy(
items = newItems,
totalPrice = calculateTotal(newItems)
)
}
}
fun setLoading(isLoading: Boolean) {
_cartState.update { it.copy(isLoading = isLoading) }
}
}
// โ ๋นํจ์จ์ : ์ฌ๋ฌ ๋ฒ์ ๊ฐ๋ณ ์
๋ฐ์ดํธ
fun inefficientUpdate() {
_state.update { it.copy(isLoading = true) }
_state.update { it.copy(errorMessage = null) }
_state.update { it.copy(data = newData) }
}
// โ
ํจ์จ์ : ํ ๋ฒ์ ๋ชจ๋ ์์ฑ ์
๋ฐ์ดํธ
fun efficientUpdate() {
_state.update { state ->
state.copy(
isLoading = true,
errorMessage = null,
data = newData
)
}
}
// โ ๏ธ ์ฃผ์: update ํจ์ ๋ด์์ ๋ฌด๊ฑฐ์ด ์ฐ์ฐ ํผํ๊ธฐ
fun avoidHeavyOperationInUpdate() {
_state.update { state ->
val heavyResult = performHeavyCalculation(state.data)
state.copy(result = heavyResult)
}
}
// โ
๊ถ์ฅ: ๋ฌด๊ฑฐ์ด ์ฐ์ฐ์ ๋ฏธ๋ฆฌ ์ํ
fun recommendedApproach() {
val heavyResult = performHeavyCalculation(_state.value.data)
_state.update { state ->
state.copy(result = heavyResult)
}
}
// โ ๏ธ ์ฃผ์: update ๋ด๋ถ์์ ๋ค๋ฅธ StateFlow ์ฝ๊ธฐ ํผํ๊ธฐ
fun avoidDeadlock() {
_stateA.update { stateA ->
val stateB = _stateB.value
stateA.copy(combined = stateA.data + stateB.data)
}
}
fun conditionalUpdate() {
_counter.update { current ->
when {
current < 0 -> 0
current > 100 -> 100
else -> current + 1
}
}
}
sealed class LoadingState {
object Idle : LoadingState()
object Loading : LoadingState()
data class Success(val data: String) : LoadingState()
data class Error(val message: String) : LoadingState()
}
class StateMachineViewModel : ViewModel() {
private val _loadingState = MutableStateFlow<LoadingState>(LoadingState.Idle)
fun startLoading() {
_loadingState.update { state ->
when (state) {
is LoadingState.Idle -> LoadingState.Loading
else -> state
}
}
}
}
MutableStateFlow์ value์ update ๋ฐฉ์์ ์ ์ ํ ํ์ฉํ๋ฉด ๋ค์๊ณผ ๊ฐ์ ์ด์ ์ ์ป์ ์ ์์ต๋๋ค.
value ์ฌ์ฉ ๊ณ ๋ ค (๋จ์ผ ์ค๋ ๋ ํ๊ฒฝ) update ํ์ ์ฌ์ฉ update์ ํจ์ํ ์ ๊ทผ ํ์ฉ update ์ธ๋ถ์์ ๋ฏธ๋ฆฌ ์ฒ๋ฆฌ update ํจ์์ ํต์ฌ ๋์while (true) {
val prevValue = value // 1. ํ์ฌ ๊ฐ ํ๋
val nextValue = function(prevValue) // 2. ๋ณํ ํจ์ ์คํ
if (compareAndSet(prevValue, nextValue)) { // 3. ์์์ ๋น๊ต ๋ฐ ๊ต์ฒด
return // ์ฑ๊ณต ์ ์ข
๋ฃ
}
// ์คํจ ์ ์ฌ์๋ (๋ค๋ฅธ ์ค๋ ๋๊ฐ ๊ฐ์ ๋ณ๊ฒฝํ์)
}
value์ update๋ฅผ ์ ํํ์ธ์. update๊ฐ ์์ ํฉ๋๋ค. value๊ฐ ์ ํฉํฉ๋๋ค. ๐ฌ ๋ง์ง๋ง ํ ์ค ์์ฝ
์ฌ๋ฐ๋ฅธ ๋ฐฉ์์ ์ํ ๋ณ๊ฒฝ์ ๋ฒ๊ทธ๋ฅผ ์ค์ด๊ณ , ์ฝ๋ ํ์ง์ ๋์ด๋ฉฐ,
๋์์ฑ ๋ฌธ์ ์์ด ์์ ์ ์ธ ์ฑ ๋์์ ๋ณด์ฅํฉ๋๋ค.