Что такое LiveLock?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
LiveLock — блокировка без deadlock
LiveLock (живая блокировка) — это состояние, когда два или более потока активны и выполняют действия, но не могут сделать прогресс, потому что постоянно реагируют друг на друга. В отличие от deadlock, потоки не заблокированы — они работают, но бесполезно.
Отличие от DeadLock
DeadLock:
- Потоки заблокированы и ждут друг друга
- Никакой активности
- Программа зависает
LiveLock:
- Потоки активны и выполняют действия
- Но не делают полезной работы
- Постоянно реагируют друг на друга
- Высокое потребление CPU
Классический пример LiveLock
// Два потока бесконечно проверяют условие и отступают
var resource1Busy = false
var resource2Busy = false
// Поток 1
thread {
while (true) {
if (!resource1Busy && !resource2Busy) {
resource1Busy = true
// Начал работу
delay(10)
// Нужна resource2
if (resource2Busy) {
println("Поток 1: resource2 занята, отпускаю resource1")
resource1Busy = false // Вежливо отпустил
delay(1) // Небольшая задержка
}
}
}
}
// Поток 2
thread {
while (true) {
if (!resource1Busy && !resource2Busy) {
resource2Busy = true
// Начал работу
delay(10)
// Нужна resource1
if (resource1Busy) {
println("Поток 2: resource1 занята, отпускаю resource2")
resource2Busy = false // Вежливо отпустил
delay(1) // Небольшая задержка
}
}
}
}
// Результат: оба потока постоянно отпускают ресурсы
// и начинают заново, ничего не делая
Реальный пример: синхронизация сетевых запросов
class DataSync {
var isSyncing = false
var hasConflict = false
suspend fun syncData() {
while (true) {
if (!isSyncing) {
isSyncing = true
try {
val localData = getLocalData()
val remoteData = getRemoteData()
if (localData != remoteData) {
hasConflict = true
isSyncing = false // Отпустил ресурс
delay(100)
// Другой поток тоже попытается синхронизировать
// Может быть бесконечный цикл попыток
continue
}
uploadData(localData)
isSyncing = false
break
} catch (e: Exception) {
isSyncing = false
delay(100)
}
} else {
delay(10)
}
}
}
}
Как обнаружить LiveLock
Признаки LiveLock:
- CPU нагружен на 100%, но никакого прогресса
- Логи показывают повторяющиеся действия
- Приложение не зависает, но не продвигается вперёд
- Потоки выполняют миллионы операций в секунду без результата
// Профилирование показывает активность
val startTime = System.currentTimeMillis()
var iterations = 0
while (System.currentTimeMillis() - startTime < 5000) {
// Попытки синхронизации
iterations++
}
println("Итераций за 5 сек: $iterations") // Огромное число!
Как избежать LiveLock
1. Добавить случайную задержку:
thread {
while (true) {
if (!resource1Busy && !resource2Busy) {
resource1Busy = true
// Работа
if (resource2Busy) {
resource1Busy = false
// Важно: случайная задержка разбивает синхронизацию
delay((Math.random() * 100).toLong())
continue
}
}
}
}
2. Использовать явный порядок захватывания ресурсов:
// Всегда захватываем resource1, затем resource2
lock(resource1) {
lock(resource2) {
// Безопасная работа с обоими ресурсами
}
}
3. Использовать retry с exponential backoff:
suspend fun syncWithRetry(maxAttempts: Int = 5) {
var attempt = 0
var delay = 100L
while (attempt < maxAttempts) {
try {
sync()
return // Успех
} catch (e: ConflictException) {
attempt++
delay(delay)
delay *= 2 // Exponential backoff
}
}
}
4. Использовать Mutex с timeout:
val mutex = Mutex()
suspend fun safeSyncData() {
if (mutex.tryLock(timeoutMillis = 1000)) {
try {
performSync()
} finally {
mutex.unlock()
}
} else {
println("Не смог захватить блокировку")
}
}
В контексте Android
LiveLock может возникнуть при:
- Синхронизации данных между несколькими источниками
- Конфликтах при работе с БД (Room, SQLite)
- Неправильной обработке сетевых ошибок
- Взаимных уведомлениях между observers
Заключение
Livelock — это подвид проблемы concurrency, менее очевидный чем deadlock, но не менее опасный. Ключ к решению — предсказуемость порядка действий и введение случайности или явных временных интервалов для разрешения конфликтов.