Что такое livelock (живая блокировка) в асинхронном программировании?

«Что такое livelock (живая блокировка) в асинхронном программировании?» — вопрос из категории Асинхронность, который задают на 29% собеседований Flutter Разработчик. Ниже — развёрнутый ответ с разбором ключевых моментов.

Ответ

Livelock (живая блокировка) — это ситуация в конкурентном или асинхронном программировании, когда два или более потока (или изолята/задачи в Dart) не блокируются полностью, но постоянно меняют свое состояние в ответ на действия друг друга, не продвигаясь в выполнении полезной работы. Это похоже на "вежливый" тупик.

Гипотетический пример на Dart с Future и общим ресурсом:

import 'dart:async';

// Общий ресурс — флаг, кто может писать в лог
bool canWrite = true;

Future<void> taskA() async {
  while (true) {
    if (canWrite) {
      canWrite = false; // Захватываем право на запись
      print('Task A is writing...');
      await Future.delayed(Duration(milliseconds: 10));
      canWrite = true; // Освобождаем
      break; // Выходим после успешной работы
    } else {
      // Вежливо уступаем и ждем
      print('Task A: waiting...');
      await Future.delayed(Duration(milliseconds: 1));
      // Проблема: к этому моменту taskB тоже мог уступить,
      // и оба снова попытаются захватить ресурс одновременно.
    }
  }
}

Future<void> taskB() async {
  while (true) {
    if (canWrite) {
      canWrite = false;
      print('Task B is writing...');
      await Future.delayed(Duration(milliseconds: 10));
      canWrite = true;
      break;
    } else {
      print('Task B: waiting...');
      await Future.delayed(Duration(milliseconds: 1));
    }
  }
}

void main() async {
  // Запускаем обе задачи "одновременно"
  await Future.wait([taskA(), taskB()]);
  print('Done'); // Эта строка может никогда не выполниться при неудачном тайминге.
}

Как избежать livelock в Flutter/Dart:

  1. Использовать примитивы синхронизации: Вместо ручных проверок флагов используйте Future с очередями, Completer или пакеты вроде async, которые предоставляют семафоры (Pool) или мьютексы.
  2. Вводить случайность или разные задержки: Если задачи уступают друг другу, добавление случайной задержки может развести их во времени и打破 симметрию.
  3. Проектировать бесконфликтный доступ: Пересмотреть архитектуру так, чтобы задачи не боролись за один ресурс. Например, использовать отдельные очереди (StreamController) для каждого изолята или механизм Provider с атомарными обновлениями состояния в Flutter.
  4. Устанавливать таймауты: Ограничивать время попыток выполнения операции.

В реальной разработке на Flutter явные livelock встречаются редко, но понимание этой проблемы помогает проектировать корректную асинхронную логику обновления состояния и работы с изолятами.