Actor Pattern — Модель акторов

Изолированные сущности с состоянием, обменивающиеся событиями. В JS не встроено, но реализуется через очереди + async + строгую изоляцию state. Согласно автору — Алан Кей изначально подразумевал ООП именно как акторы.

Проблема

В асинхронном коде с await shared state нарушается. Пример: 5 заказов параллельно тратят товар со склада, у каждого до decrement срабатывает свой await — все продали один и тот же товар, склад ушёл в -5.

Решение

  • Один actor = единственный владелец state
  • Все запросы к state идут через очередь сообщений актора
  • Актор обрабатывает их последовательно (одна задача за раз)
  • Снаружи — async-API (await на response), внутри — синхронная обработка

Пример в JS

class StockActor {
  #state;        // приватный, никто извне не трогает
  #queue = ;
  #processing = false;

  constructor(initial) { this.#state = new Map(initial); }

  send(message) {
    return new Promise((resolve, reject) => {
      this.#queue.push({ message, resolve, reject });
      this.#process;
    });
  }

  async #process {
    if (this.#processing) return;
    this.#processing = true;
    while (this.#queue.length) {
      const { message, resolve, reject } = this.#queue.shift();
      try {
        resolve(await this.#handle(message));
      } catch (e) { reject(e); }
    }
    this.#processing = false;
  }

  async #handle(msg) {
    if (msg.type === 'buy') {
      const qty = this.#state.get(msg.item) ?? 0;
      if (qty < msg.qty) throw new Error('out of stock');
      this.#state.set(msg.item, qty - msg.qty);
      return 'ok';
    }
  }
}

// клиенты обращаются через очередь — race conditions невозможны
const stock = new StockActor([['apple', 10]]);
await Promise.all([
  stock.send({ type: 'buy', item: 'apple', qty: 3 }),
  stock.send({ type: 'buy', item: 'apple', qty: 5 }),
]);

Где используется в JS-экосистеме

  • Web Workers — каждый воркер изолирован, общение через postMessage
  • worker_threads в Node.js — то же на бэке
  • Erlang-style libraries: actor-system, js-csp
  • Redux saga — конкурентные задачи через generator+channels
  • XState v5 actors — actor model в state machines
  • WebSocket-сервер: каждый коннект как актор

Подводные камни

  • Узкое место: если актор обрабатывает много задач — становится bottleneck.
  • Composability: акторы плохо комбинируются — нужны actor-systems (как Erlang OTP).
  • Backpressure: очередь может расти бесконечно — нужны лимиты.
  • State не сериализуется между worker_threads автоматическиpostMessage копирует.

Главные тезисы автора

  • «Алан Кей под ООП имел в виду модель акторов» — изолированные сущности, события.
  • JS sync-код поточно-безопасен, но с await ownership state ломается.
  • «Ownership state через async/await» — иллюзорный, ломается при concurrent вызовах.
  • «State лучше внутри функции» — не расшаривать между контекстами.
  • Гонка: все проверки наличия идут до списания, поэтому проверка не спасает.
  • Actor + State + Observer — частая комбинация в распределённых системах.

🎓 Источники

См. также