Как формируется Intertangle
Две подсистемы изначально существуют как независимые модули. Со временем каждая из них добавляет поле в общий god-объект: глобальную структуру конфигурации, синглтон-менеджер или статический класс. Каждое добавление по отдельности выглядит корректным. Связанность остаётся невидимой при мелкомасштабном тестировании.
Три примера, где эта проблема закрепилась:
VLC media player. Аудио, видео и плейлист используют один общий лок, защищающий глобальное состояние плеера. Запрос на переход к временной метке захватывает лок, изменяет позицию воспроизведения и сбрасывает аудиобуфер. Видеоподсистема, ожидающая тот же лок, блокируется. Плейлист также не может выполнять предзагрузку. Результат: три независимые подсистемы сериализуются через один объект состояния. Стоимость производительности: O(N) конкуренции за лок, где N — количество подсистем, и всё пропорционально задержке операции.
Цикл событий Redis. AOF fsync (запись на диск), репликация (запись по сети) и выполнение команд (CPU) используют один и тот же однопоточный цикл событий. Каждое действие по отдельности корректно. Медленный fsync блокирует выполнение команд. Лаг репликации растёт при высокой нагрузке на запись. Точка сцепления: единый контекст выполнения, разделяемый операциями с разными профилями задержек.
VersionSet в LevelDB. Путь записи (сброс memtable) и фоновая компакция разделяют блокировку VersionSet. Задача компакции удерживает блокировку десятки миллисекунд. Путь записи блокируется. Обе операции необходимы. Сцепление структурное, а не временное.
Ключевое отличие
Intertangle — это структурное сцепление, а не проблема синхронизации по времени. Состояние гонки: два потока обращаются к общему состоянию без синхронизации. Решение: добавить мьютекс.
Intertangle: две подсистемы по замыслу разделяют состояние. Добавление мьютекса не устраняет сцепление — оно лишь сериализует доступ. Подсистемы по-прежнему делят состояние. Узкое место становится жёстче.
Добавление мьютекса к VLC Intertangle ухудшает ситуацию: теперь аудио, видео и плейлист ждут одной блокировки. Структурное исправление: дать каждой подсистеме собственное состояние. Снимок фазы: зафиксировать снимок общего состояния на границе фазы, позволить каждой подсистеме читать снимок независимо и записывать изменения обратно в конце.
Структурное vs временное
Ключевой диагностический вопрос для Intertangle: добавление мьютекса исправит проблему или сделает её хуже?
Состояние гонки: добавление мьютекса исправляет её. Правильный порядок доступа устраняет повреждение данных.
Intertangle: добавление мьютекса сериализует доступ, но сохраняет структурную связанность. Подсистемы по-прежнему разделяют состояние. Под нагрузкой они по-прежнему блокируют друг друга. Узкое место остаётся.
Как найти Intertangle
Три сигнала обнаружения:
1. Общие изменяемые поля между подсистемами. God-объект с полями, которые читаются и записываются более чем одной подсистемой. Если удаление доступа одной подсистемы к полю ломает другую подсистему, они разделяют состояние.
2. Один мьютекс, охраняющий несвязанные операции. Один лок, защищающий audio flush И video decode И playlist fetch: три подсистемы с разными профилями задержек, все ждут друг друга. Запах: несвязанные операции под одним именем лока.
3. Регрессия производительности при добавлении нагрузки. Задержка операции A растёт, когда операция B выполняется параллельно, хотя A и B кажутся независимыми. Они не независимы: они разделяют состояние.
Исправление Phase-Snapshot
Паттерн phase snapshot:
# ДО: подсистемы напрямую читают и записывают общее состояние
class GameWorld:
position = {} # shared mutable
velocity = {} # shared mutable
def physics_tick(world):
for entity in world.entities:
world.position[entity] += world.velocity[entity] # writes shared state mid-loop
# AFTER: снимок заморожен перед фазой; записи идут в буфер next_state
def physics_tick(world):
snapshot = world.freeze() # неизменяемый вид
next_state = {}
for entity in snapshot.entities:
next_state[entity] = snapshot.position[entity] + snapshot.velocity[entity]
world.merge(next_state) # атомарное слияние на границе фазы
Каждая подсистема читает снимок. Ни одна подсистема не пишет в него. Записи накапливаются в буфере и сливаются атомарно на границе фазы. Подсистемы теперь выполняются независимо: нет конкуренции за блокировки, нет зависимости от порядка, нет скрытой связанности.
Применить исправление
Команда сообщает об ошибке: в игровом движке система анимации и система коллизий обе записывают в общий объект трансформации сущности. Когда обе выполняются в одном тике, результаты коллизий зависят от того, выполнилась ли анимация первой. Добавление мьютекса исправило порядок, но теперь анимация блокируется при каждом широком проходе коллизий.