Практические примеры основных проблем параллельного программирования с рабочими решениями.
# Установка (если нужно)
npm install
# Запуск отдельных примеров
npm run racing # Race Condition
npm run deadlock # Deadlock
npm run starvation # Starvation
npm run livelock # Livelock
# Или напрямую
node 1-racing.js
node 2-deadlock.js
node 3-starvation.js
node 4-livelock.jsНесколько асинхронных операций одновременно читают и модифицируют общие данные без синхронизации.
Пример: Два одновременных снятия по 600₽ с баланса 1000₽ → баланс становится -200₽ вместо 400₽.
Гарантирует, что только один поток/задача имеет доступ к критической секции.
class Mutex {
async lock() { /* ждем, пока не освободится */ }
unlock() { /* освобождаем */ }
}Для простых операций (инкремент, сравнение) используем атомарные инструкции CPU.
Atomics.compareExchange(array, index, expected, newValue);Когда использовать:
- Mutex: сложная бизнес-логика
- Atomics: простые операции с числами
Две задачи ждут ресурсы друг друга:
- Task-1 захватил A, ждет B
- Task-2 захватил B, ждет A
- Обе зависли навсегда
ВСЕ задачи захватывают ресурсы в одинаковом порядке (например, всегда A → B).
// Все всегда делают: lock(A) -> lock(B)
// НИКОГДА: lock(B) -> lock(A)Если не удалось захватить ресурс за N мс → откатываемся и пробуем снова.
const success = await mutex.lock(500); // timeout 500ms
if (!success) {
// откат и повтор
}Пытаемся захватить без блокировки. Если занят → откатываемся.
if (!mutex.tryLock()) {
// не смогли захватить, откатываемся
}Когда использовать:
- Ordering: когда можно контролировать порядок
- Timeout: когда важна отказоустойчивость
- Try-lock: для высоконагруженных систем
Высокоприоритетные задачи постоянно захватывают ресурс, низкоприоритетные никогда не получают доступ.
Пример: 3 быстрые задачи делают 60 операций, 1 медленная — 0 операций за то же время.
Ресурс выдается строго по порядку прихода запросов.
class FairMutex {
queue = []; // FIFO
async lock() {
await queue.push(promise);
}
}Чем дольше задача ждет, тем выше ее эффективный приоритет.
effectivePriority = basePriority + waitTime;Ограничиваем количество операций на задачу в единицу времени.
// Макс 5 операций в секунду на задачу
await rateLimitedMutex.lock(taskName, 5);Когда использовать:
- Fair FIFO: когда все задачи равноправны
- Aging: когда есть приоритеты, но нужно избежать голодания
- Rate limiting: когда нужно ограничить агрессивных потребителей
Задачи активно работают, но не прогрессируют — постоянно отменяют свои действия.
Пример:
- Task-1: захватил A, не может взять B → освобождает A
- Task-2: захватил B, не может взять A → освобождает B
- Повторяется бесконечно
Случайные задержки разрывают синхронизацию конфликтующих задач.
const backoff = Math.random() * 150;
await sleep(backoff);Центральный арбитр атомарно выдает ВСЕ необходимые ресурсы сразу.
const resources = await coordinator.requestResources();
// получили A и B атомарноКомбинация упорядочения и timeout'ов предотвращает зацикливание.
Акторы общаются через асинхронные сообщения, избегая блокировок.
Когда использовать:
- Random backoff: простое и эффективное решение для большинства случаев
- Coordinator: когда нужны гарантии прогресса
- Lock ordering: когда можно контролировать порядок
- Actor model: для распределенных систем
| Проблема | Суть | Прогресс системы | Лучшее решение |
|---|---|---|---|
| Race Condition | Конкурентная модификация данных | Есть, но данные некорректны | Mutex / Atomics |
| Deadlock | Циклическое ожидание ресурсов | Нет (полная остановка) | Resource Ordering |
| Starvation | Задачи не получают ресурсы | Есть, но несправедливый | Fair FIFO / Priority Aging |
| Livelock | Постоянная отмена действий | Видимый, но бесполезный | Random Backoff / Coordinator |
- Минимизируйте критические секции — чем меньше код под блокировкой, тем меньше конфликтов
- Избегайте вложенных блокировок — если нельзя избежать, используйте строгий порядок
- Используйте таймауты — бесконечное ожидание = потенциальная проблема
- Lock-free структуры данных — если возможно, используйте атомарные операции
- Тестируйте под нагрузкой — проблемы параллелизма проявляются при высокой конкуренции
Простые операции с числами?
→ Atomics
Сложная бизнес-логика?
→ Mutex
Несколько ресурсов?
→ Resource Ordering + Timeout
Есть приоритеты?
→ Priority Aging
Распределенная система?
→ Actor Model / Message Passing
- Node.js >= 14.0 (для SharedArrayBuffer)
- Нет внешних зависимостей