{"id":14285,"url":"\/distributions\/14285\/click?bit=1&hash=346f3dd5dee2d88930b559bfe049bf63f032c3f6597a81b363a99361cc92d37d","title":"\u0421\u0442\u0438\u043f\u0435\u043d\u0434\u0438\u044f, \u043a\u043e\u0442\u043e\u0440\u0443\u044e \u043c\u043e\u0436\u043d\u043e \u043f\u043e\u0442\u0440\u0430\u0442\u0438\u0442\u044c \u043d\u0430 \u043e\u0431\u0443\u0447\u0435\u043d\u0438\u0435 \u0438\u043b\u0438 \u043f\u0443\u0442\u0435\u0448\u0435\u0441\u0442\u0432\u0438\u044f","buttonText":"","imageUuid":""}

Actor'ы в Swift

Начиная с версии Swift 5.5 появился новый ссылочный тип (reference type) - акторы. В данной статье будет рассказано, для чего их добавили и какую роль они выполняют.

Для того чтобы узнать, в каких случаях используются акторы, давайте рассмотрим типичный пример data race с использованием non-thread safe коллекции.

Здесь представлен класс Library которые выступает в качестве "контейнера" для объектов класса Book. Данный код работает корректно в однопоточной среде. Для многопоточного приложения данная реализация будет аварийно завершать программу.
Существует множество способов для устранения этой проблемы. Одним из них - это создание приватной concurrent очереди и методы add(_ book:) и get(name:) обернуть в синхронный вызов данной очереди. Очевидным минусом данного подхода является производительность. Во время выполнения одного из методов, второй будет дожидаться окончания его завершения.
Второй способ устранения данной патологии - это реализация SWMR подхода, однако реализация чуть усложняется с использованием dispatch_barrier.

Для устранения всех рассмотренных недостатков как раз и вступают в игру акторы. Акторы, как уже упоминалось ранее, являются ссылочным типом, а так же они синхронизируют все вызовы к методам и свойствам. Это означает что только один вызывающий объект имеет атомарный доступ к операциям которые предоставляет актор. Нам достаточно лишь поменять тип у Library для получения этих преимуществ.

Одного лишь этого изменения достаточно чтобы сделать данный контейнер потокобезопасным. Теперь можно использовать данный класс в многопоточной среде не опасаясь data race.

Так же, стоит учесть, что взаимодействовать с акторами мы можем только в асинхронном режиме, а именно вызывать методы add(_ book:) или get(name:) мы должны в методе с async семантикой:

В заключении стоит отметить, что акторы являются удобным способом для предоставления потокобезопасности. Однако, как отмечалось ранее, все обращения к методам актора должны выполняться в асинхронной среде, что потенциально может усложнить поддержку и понимание кодовой базы.

0
10 комментариев
Написать комментарий...
Dmitrii Khizbullin

Отличный пример асинхронного кода! Спасибо

Ответить
Развернуть ветку
Аккаунт удален

Комментарий недоступен

Ответить
Развернуть ветку
David Grigoryan
Автор

В стандартной акторной модели, как правило содержится очередь операций. Так вот в данном случае данная очередь избавляет нас от тех проблем, которые были затронуты в статье, а именно операция на входе ждёт пока завершится текущая операция в акторе.
Так же, ключевое слово await тоже можно рассматривать как отправку сообщений в стандартной акторной модели.

Ответить
Развернуть ветку
Аккаунт удален

Комментарий недоступен

Ответить
Развернуть ветку
David Grigoryan
Автор

Я полагаю что нейминг был выбран исходя из того, на "что из существующих механизмов это больше всего похоже" :)

Ответить
Развернуть ветку
Dmitry Mikushin

С точки зрения алгоритмов и структур данных, блокировка get() и add() в этом примере несимметрична. Предполагая, что book - стандартная хеш-таблица, вызовы add() блокируют друг друга и get(). А вот get() друг друга не блокируют, потокобезопасно читать можно сколько угодно. Интересно, учитывает ли этот факт реализация Actor, и учитывает ли программист на Swift, если ему достаточно только применить Actor.

Ответить
Развернуть ветку
David Grigoryan
Автор

Вся проблема в том, что нету гарантий того, что не будет производится одновременно чтение и запись. И в таком случае чтение не может быть безопасным, если по этому же самому ключу в таблице будет происходить запись новых данных.

Ответить
Развернуть ветку
Аккаунт удален

Комментарий недоступен

Ответить
Развернуть ветку
David Grigoryan
Автор

Да, такое конечно можно реализовать, как минимум с помощью барьеров которые встроены в libdispatch

Ответить
Развернуть ветку
David Grigoryan
Автор

+ ко всему, в любом случае, в классической (и правильной) парадигме RW мьютексов - операция записи должна дождаться, пока выполнится текущая операция чтения.

Ответить
Развернуть ветку
7 комментариев
Раскрывать всегда