ActixMiddleTechnical

Что такое актор-модель в Actix и чем она отличается от стандартного асинхронного программирования?

Актор в Actix — изолированный объект с собственным состоянием, который получает сообщения через Addr<A> и обрабатывает их последовательно без Mutex. В отличие от async/await, состояние не разделяется — гонки данных исключены архитектурно.

Актор-модель в Actix

Actix построен на актор-модели из библиотеки actix. Актор — это изолированный объект с собственным состоянием, который общается с другими только через сообщения. Каждый актор имеет почтовый ящик (Addr<A>), обрабатывает сообщения последовательно и никогда не разделяет состояние напрямую.

Ключевые типажи: Actor (жизненный цикл: started, stopping, stopped), Handler<M> (обработка конкретного типа сообщения), Message (тип с ассоциированным Result).

Отличие от стандартного async/await

В стандартном Tokio-коде вы создаёте async fn, которая выполняется на пуле потоков и может владеть любыми данными. Состояние либо клонируется, либо защищается Arc<Mutex<T>>. В актор-модели состояние принадлежит только актору — никакого Mutex не нужно, гонки данных исключены на уровне архитектуры.

  • Async/await: явный контроль await-точек, состояние — в переменных или Arc
  • Акторы: состояние инкапсулировано в структуре актора, доступ только через send() / do_send()
  • Акторы удобны для долгоживущих объектов с изменяемым состоянием (WebSocket-сессии, кэши, очереди)

Пример актора-счётчика

use actix::prelude::*;

struct Counter {
    count: usize,
}

impl Actor for Counter {
    type Context = Context<Self>;
}

// Сообщение — инкремент
struct Increment;
impl Message for Increment {
    type Result = usize;
}

impl Handler<Increment> for Counter {
    type Result = usize;

    fn handle(&mut self, _: Increment, _: &mut Context<Self>) -> usize {
        self.count += 1;
        self.count
    }
}

#[actix::main]
async fn main() {
    let addr = Counter { count: 0 }.start();
    let result = addr.send(Increment).await.unwrap();
    println!("Count: {}", result); // Count: 1
}

Метод send() возвращает Future с результатом. do_send() — fire-and-forget без ожидания ответа.

Actix-web и акторы

В современном actix-web 4 обработчики маршрутов — это обычные async fn, а не акторы. Actix-сам по себе использует акторы внутренне (например, для WebSocket), но строить бизнес-логику через акторы — выбор разработчика. Для WebSocket-хендлеров реализуется StreamHandler<Result<ws::Message, ws::ProtocolError>>.

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

  • Блокирующий код внутри handle() заблокирует весь актор — используйте ctx.spawn() или выносите тяжёлые операции в actix_rt::task::spawn_blocking
  • do_send() молча теряет сообщение, если почтовый ящик переполнен — всегда мониторьте backpressure
  • Акторы работают в одном потоке своего Arbiter; для параллельной обработки создавайте пул акторов через SyncArbiter::start(n, ...)
  • Циклические ссылки через Addr не освобождаются автоматически — актор не остановится, пока жив хотя бы один Addr
  • Тестирование акторов требует #[actix::test]-рантайма, обычный #[tokio::test] не подойдёт
  • Версии actix и actix-web должны совпадать — несовместимость вызывает ошибки компиляции с неочевидными сообщениями
  • Состояние актора не персистентно — перезапуск системы уничтожает все данные в памяти

Common mistakes

  • Отвечать определением без production-сценария.
  • Не называть runtime boundary, security boundary или failure mode.
  • Игнорировать версию API, observability и тестовую проверку.

What the interviewer is testing

  • Объясняет механизм своими словами и без выдуманных API.
  • Называет реальные риски, диагностику и критерий корректности.
  • Связывает ответ с текущей документацией и миграционными ограничениями.

Sources

Related topics