Як утворюється переплетення
Дві підсистеми спочатку існують як незалежні модулі. З часом кожна накопичує поле на спільному god-об’єкті: глобальній структурі конфігурації, singleton-менеджері, статичному класі. Кожне додавання: правильне ізольовано. Зчеплення: непомітне під час тестування в малому масштабі.
Три субстрати, де це закріпилося:
VLC media player. Аудіо, відео та плейлист поділяють один lock, що захищає глобальний стан плеєра. Запит на перехід до мітки часу отримує lock, змінює позицію відтворення та скидає аудіобуфер. Відеопідсистема, чекаючи той самий lock, блокується. Підсистема плейлиста, також чекаючи, не може виконати prefetch. Результат: три незалежні підсистеми серіалізуються через один об’єкт стану. Вартість продуктивності: O(N) конкуренція за lock, де N = кількість підсистем, усе пропорційно до затримки операції.
Цикл подій Redis. AOF fsync (запис на диск), реплікація (мережевий запис) і виконання команд (CPU) використовують однопотоковий цикл подій. Кожен процес: коректний окремо. Повільний fsync блокує виконання команд. Затримка реплікації зростає під навантаженням запису. Точка зв’язку: єдиний контекст виконання, що поділяється операціями з різними профілями затримки.
VersionSet у LevelDB. Шлях запису (скидання memtable) і фонова компакція спільно використовують блокування VersionSet. Завдання компакції утримує блокування десятки мілісекунд. Шлях запису блокується. Обидві операції: необхідні. Зв’язок: структурний, а не часовий.
Критична відмінність
Intertangle має структурний зв’язок, а не проблему часу. Race condition: два потоки звертаються до спільного стану без синхронізації. Виправлення: додати м’ютекс.
Intertangle: дві підсистеми за дизайном спільно використовують стан. Додавання м’ютекса не усуває зв’язок; воно лише серіалізує доступ. Підсистеми все одно спільно використовують стан. Вузьке місце посилюється.
Додавання м’ютекса до VLC Intertangle погіршує ситуацію: тепер аудіо, відео та плейлист очікують на одне блокування. Структурне виправлення: надати кожній підсистемі власний стан. Знімок фази: зафіксувати знімок спільного стану на межі фази, дозволити кожній підсистемі читати знімок незалежно, об’єднати записи назад наприкінці.
Структурне vs Часове
Ключове діагностичне питання для Intertangle: чи додавання м’ютекса виправить проблему, чи погіршить її?
Стан гонитви: додавання м’ютексу виправляє його. Коректний порядок доступу усуває пошкодження.
Інтертангл: додавання м’ютексу серіалізує доступ, але зберігає структурне зчеплення. Підсистеми все одно спільно використовують стан. Під навантаженням вони все одно блокують одна одну. Вузьке місце звужується.
Як знайти Інтертангл
Три сигнали виявлення:
1. Спільні змінювані поля між підсистемами. God-об’єкт із полями, які зчитуються та записуються більше ніж однією підсистемою. Якщо видалення доступу до поля однієї підсистеми ламає іншу підсистему, вони спільно використовують стан.
2. Один м’ютекс, що захищає непов’язані операції. Один лок, який захищає audio flush І video decode І playlist fetch: три підсистеми з різними профілями затримки, усі чекають одна на одну. Запах: непов’язані операції під однією назвою лока.
3. Регресія продуктивності при додаванні навантаження. Затримка операції A зростає, коли операція B виконується одночасно, навіть якщо A і B здаються незалежними. Вони не незалежні: вони спільно використовують стан.
Виправлення Phase-Snapshot
Шаблон phase snapshot:
# ДО: підсистеми напряму зчитують і записують спільний стан
class GameWorld:
position = {} # спільний змінний стан
velocity = {} # спільний змінний стан
def physics_tick(world):
for entity in world.entities:
world.position[entity] += world.velocity[entity] # записує спільний стан всередині циклу
# ПІСЛЯ: знімок заморожено перед фазою; записи йдуть у буфер 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) # атомарне злиття на межі фази
Кожна підсистема читає знімок. Жодна підсистема не записує в нього. Записи накопичуються в буфері та атомарно зливаються на межі фази. Підсистеми тепер виконуються незалежно: немає конкуренції за блокування, немає залежності від порядку, немає прихованого зв’язку.
Застосуйте виправлення
Команда повідомляє про дефект: анімаційна система та система колізій ігрового рушія обидві записують у спільний об’єкт трансформації сутності. Коли обидві виконуються в одному тіку, результати колізій залежать від того, чи виконалася анімація першою. Додавання м’ютексу виправило порядок, але тепер анімація блокується щоразу, коли колізія виконує широкофазний прохід.