Как Actor в Swift обеспечивает потокобезопасность в async/await?

Ответ

Actor обеспечивает потокобезопасность через изоляцию состояния и сериализацию доступа. Каждый actor имеет внутреннюю очередь (mailbox), и доступ к его изменяемому состоянию возможен только через асинхронные вызовы (await). Компилятор гарантирует, что в один момент времени выполняется только одна задача, изменяющая состояние actor, предотвращая race conditions.

Пример actor:

actor Counter {
    private var value = 0

    func increment() {
        value += 1
    }

    func getValue() -> Int {
        return value
    }
}

// Использование
let counter = Counter()
Task {
    await counter.increment()
    print(await counter.getValue()) // Гарантированно 1
}

Ключевые механизмы:

  1. Изоляция: Свойства и методы actor изолированы по умолчанию.
  2. Сериализация: Вызовы методов, изменяющих состояние, ставятся в очередь.
  3. await обязателен: Любой доступ к actor извне требует await, что явно указывает на потенциальную приостановку.
  4. Не блокирующий: В отличие от locks, actor использует кооперативную модель async/await, что эффективнее для конкурентности.

Ответ 18+ 🔞

Да ты посмотри, какая красота, блядь! Взяли и придумали целую сущность, чтобы не ебаться с этими локами, семафорами и прочей синхронизационной хуйнёй. Actor, сука! Звучит-то как, будто из Голливуда.

Смысл в чём, ёпта? Вся его потокобезопасность держится на двух китах, блядь: изоляция состояния и сериализация доступа. У каждого актора есть своя личная почта (mailbox), этакая очередь для входящих. И если ты хочешь потрогать его внутренние изменяемые штуки, то только через асинхронные вызовы, с этим самым await. Компилятор, хитрая жопа, следит, чтобы в один момент времени только одна задача могла там внутри ковыряться. Race conditions? Да пошли они нахуй, их просто не будет!

Вот, смотри, как это выглядит в коде, простейший счётчик:

actor Counter {
    private var value = 0

    func increment() {
        value += 1
    }

    func getValue() -> Int {
        return value
    }
}

// Использование
let counter = Counter()
Task {
    await counter.increment()
    print(await counter.getValue()) // Гарантированно 1, ни одной лишней единички, блядь!
}

А теперь разжёвываю, как это работает, чтобы ты, блядь, понял:

  1. Изоляция, ёбта! Все его свойства и методы по умолчанию изолированы. Снаружи к value просто так не подлезешь, хоть тресни. Только через его же методы.
  2. Сериализация, мать её. Все вызовы методов, которые меняют состояние, аккуратно становятся в очередь в его почтовый ящик и выполняются по одному. Как в хорошей бане — пропустили, помылся, следующий.
  3. await — священный знак. Любой доступ к актору извне требует этого слова. Это как красная лампочка: "Внимание, чувак, тут может быть пауза, пока твой запрос ждёт своей очереди!" Явно и понятно, ебать.
  4. Не блокирующий, а кооперативный. Это не тот старый дедовский лок, который поток намертво вешает. Нет, бля! Это современная модель на async/await. Поток не блокируется, он может пойти делать другую полезную работу, пока ждёт. Эффективность — овердохуища!

Вот и вся магия. Красиво, правда? Как будто порядок навели в этом хаосе конкурентности.