За счёт чего actor предотвращает одновременное выполнение изолированных участков на разных задачах?
Actor предоставляет сериализованный доступ к своему изолированному состоянию: в каждый момент времени выполняется только один изолированный фрагмент работы этого actor. Это не означает привязку к одному потоку: разные фрагменты могут выполняться на разных потоках, но не одновременно.
В многопоточном коде общий изменяемый объект обычно защищали блокировками, очередями или соглашениями между разработчиками. Такие подходы позволяли синхронизировать доступ, но корректность зависела от дисциплины: легко было забыть блокировку, удерживать её слишком долго или вызвать код с повторным входом.
Actors перенесли правило владения состоянием на уровень модели конкурентности Swift. Компилятор проверяет, что изолированное состояние доступно только через контролируемые границы actor, а среда выполнения сериализует такие обращения.
Пусть несколько задач одновременно вызывают методы объекта, который изменяет баланс, кэш или состояние сессии. Если доступ не синхронизирован, операции могут прочитать устаревшее значение, потерять обновление или наблюдать промежуточное состояние.
Actor устраняет одновременный доступ к своему изолированному состоянию, но не делает любую последовательность операций автоматически атомарной. Особенно важно учитывать точки await: во время приостановки actor может начать выполнять другой изолированный вызов.
У actor есть изолированное состояние и исполнитель, который обрабатывает обращения к этому состоянию по одному. Когда задача вызывает изолированный метод из другого контекста, она передаёт работу границе actor; после этого компилятор требует явно обозначить потенциальную приостановку через await.
Сериализация относится к изолированным участкам, а не к физическому потоку. После приостановки выполнение может возобновиться на другом потоке, поэтому полагаться на thread affinity нельзя.
В примере два вызова не изменяют value одновременно. При этом Counter не обещает, что increment всегда выполняется на одном и том же системном потоке.
Сериализация не равна глобальной блокировке всего actor. Если изолированный метод дошёл до await, его текущий фрагмент приостанавливается, а другой вызов может выполнить свой фрагмент. Поэтому проверку и изменение состояния нужно помещать в один непрерывный изолированный участок, если между ними нет await.
Actor также не защищает автоматически ссылочный объект, который был извлечён из его состояния и передан наружу. После передачи такой объект может изменяться вне изоляции actor, если его собственная модель безопасности этого не предотвращает.
Сервис кэширования хранит словарь результатов и при отсутствии значения обращается к сети. Вариант с обычным словарём и блокировкой защищает отдельные обращения, но сложная логика загрузки легко приводит к ошибкам при ручном управлении блокировкой. Вариант с отдельной serial queue работает, однако изоляция остаётся соглашением API и хуже проверяется компилятором.
Actor делает владение словарём явным и сериализует его чтение и изменение. Практическое решение — не удерживать блокировку во время сетевого ожидания и отдельно продумать состояние загрузки: например, хранить признак выполняющегося запроса, чтобы параллельные вызовы не запускали дубликаты.
Результат: гонки доступа к словарю устраняются, но логика дедупликации запросов всё равно проектируется явно. Actor защищает состояние, а не автоматически оптимизирует бизнес-операцию.
Нет. Actor обеспечивает изоляцию и последовательное выполнение изолированных участков, но не закрепляет их за конкретным потоком. Планировщик может переместить продолжение задачи между потоками, особенно после await.
Два непрерывных изолированных участка не выполняются одновременно. Однако метод может приостановиться на await, после чего другой вызов получит возможность выполнить свой участок; затем первый метод продолжится. Поэтому actor является реентерабельным, и состояние после await нельзя считать неизменным без повторной проверки.
Нет. Каждый actor отдельно сериализует собственное состояние, но операция, затрагивающая два actor, может быть прервана между обращениями к ним. Для такой операции нужно изменить модель данных, ввести единый владеющий actor или спроектировать протокол согласования, иначе промежуточное состояние будет видимо другим задачам.