Тред с техническим описанием многопоточной aka Schedulable архитектуры.
Ремарка. В контекстве данного треда понятия поток (thread), очередь (GCD queue) и шедулер (Rx Scheduler) я использую как синонимы, дабы избежать многочисленных повторов в тексте.
Итак, в первой части мы пришли к тому, что нам нужны фоновые сервисы, но мы хотим держаться подальше от внедрения в код логики, связанной с синхронизацией доступа к данным из разных потоков. Как же это сделать?
Для представления schedulable-архитектуры у себя в голове крайне важно иметь хорошую ментальную модель. В качестве неё мне очень нравится метрополитен.
Маппинг модели метро на архитектурную модель программы:
• Станции – это прикладные сервисы.
• Ж/д дорога между станциями – вызов метода одним сервисом у другого.
• Пассажиры – обрабатываемые структуры данных.
• Маршрут от ст. А до ст. Б – флоу обработки данных в рамках фичи.
Реализация фичи с технической точки зрения – это преобразование исходных данных к желаемому виду.
В модели метрополитена флоу фичи таков:
• Данные садятся на станции А.
• Станция А их обрабатывает и с помощью поезда-рантайма передаёт станции Б.
• Станции, требующие при въезде на неё синхронизации данных, помечаются особо.
• Данные высаживаются на целевой станции Б.
Для архитектуры программы, где каждый сервис менеджит своё состояние в собственном потоке и сам заботиться о своей потокобезопасности очень хорошо подходит карта метро из книги "Метро 2033".
Это хорошая визуализация структуры программы из моего прошлого. На ней много опасных зон. Все вот эти знаки с предупреждением о биологической, радиационной и прочей опасности – это места, где дедлоки и датарейсы подстерегают путников.
Карта здоровой многопоточной архитектуры выглядит как карта актуального метрополитена наших дней. Давайте смотреть, чем же она хороша, и какими правилами поддерживается бесперебойная работа нашего "метро".
Правило №0. Sync-free. Использование примитивов синхронизации в прикладном коде находится под строжайшим запретом. Потокобезопасным может быть только такой сервис, который либо не содержит мутабельных данных вообще, либо все они защищены специальной инфраструктурой.
В приложении нет ни одного места, где бы вы смогли увидеть примитивы синхронизации. Вся машинерия, связанная с потокобезопасностью, отдана на откуп внешним по отношению к приложению библиотекам: #ReactiveCocoa, #SQLite, #LMDB и #POSLens.
Правило №1. Все данные в системе должны быть немутабельными, чтобы иметь возможность свободно перемещаться между разными потоками. Тут всё понятно, комментарии излишни.
Правило №2. В системе все сервисы, имеющие мутабельное состояние, на всё время своей жизни жёстко ассоциируются со строго определенным RACScheduler. Все сервисы, проассоциированные с одним и тем же шедулером, имеют право общаться между собой путем обычных синхронных вызовов.
Мы ведь не переживаем за синхронизацию данных в однопоточном приложении? Для неё нет места, т.к. runloop один на всех, и либо методы fiz и baz вызовутся последовательно внутри одного цикла обработки сообщений, либо в рамках следующего, который начнётся строго после текущего.
С зашедуленными сервисами ситуация та же самая. Т.к. у них один шедулер на всех, то, грубо говоря, и один ранлуп на всех. При наличии гарантий со стороны архитектуры, что все обращения к сервису будут происходить из одного ранлупа, ему нет нужды что-либо потокобезопасить.
Гарантом корректности вызовов является базовый класс POSSchedulableObject, о котором я писал в тредике в среду. Если метод будет вызван в некорректном потоке, то будете сгенерировано исключение.
Если сервис А и сервис Б одновременно проассоциированы с одним и тем же шедулером, то очевидно, что А может спокойно вызывать методы Б обычным синхронным образом, ведь раз он это делает, то, значит, сервис Б сейчас ничем другим не занимается.
Ассоциация объекта с шедулером – эволюция концепта зашедуливания каких-то отдельных методов. Можно сказать, мы объектно-ориентируем шедулинг. Вопрос "в каком бы треде вызвать функцию foo?" заменился другим – "в каком треде мы будем обращаться к объекту Foo?"
С точки зрения модели метрополитена, сервисы разделяющие один и тот же шедулер, – это станции на одной линии метро. Если данные подвергаются обработке только сервисами, расположенными на одной ветке, то это комфортное однопоточное путешествие.
В нашем коде дополнительно к дефолтной главной ветке метро с кодовым названием "UI", построено 2 дополнительные: BL (от Business Logic) и PF (от Photos Framework). Фоновые сервисы потихоньку выносятся в BL.
В интерфейсе IoC-контейнров есть конвенция суффиксовать названия фабричных методов сервисов названием их шедулера.
Однако не всем из нас выпало счастье добираться на работу без пересадок. Как общаются между собой сервисы, находящиеся на разных ветках метро?
Правило №3. Сервисы, проассоциированные с разными шедулерами, могут общаться между собой либо с помощью косвенных вызовов, либо через общее потокобезопасное хранилище данных ("shared database design pattern").
На скриншоте вариант с косвенными вызовами, который происходит за счет зашедуливания блоков с вызовами сервиса в шедулер этого сервиса.
Общение в обратную сторону обеспечивается за счет подписки на сигналы ReactiveCocoa, блистательные авторы которой сделали их потокобезапосными. Конечно же надо не забывать зашедуливать их из вражеского шедулера в свой обратно.
Отдельной строкой про потокобезопасные хранилища. Если речь идет про базы данных, то с ними всё понятно. Используемые нами SQLite и LMDB берут вопросы потокобезопасности на себя.
Однако было бы абсурдно для каждой мутабельной структуры данных заводить запись в таблице, чтобы обеспечить её потокобезопасность.
Тем не менее, правило №0, запрещающее иметь примитивы синхронизации в приложении и правило №1, предписывающие всем моделям быть немутабельными, никто не отменял.
Для мутации немутабельного в функциональном программировании существует концепт "линзы". По ссылке находится реализация этого концепта для #objc
github.com/pavelosipov/PO…
Ради сокращения длины треда, позволю себе опустить объяснение, что такое функциональные линзы. Кому интересно, можете начать вот с этой ссылки.
broomburgo.github.io/fun-ios/post/l…
В конечном счете она вас должна привести к платным курсам на pointfree.co : )
Линзы являются эдакими потокобезопасными мини-БД для хранения одиночных объектов. На скриншоте демонстрирует использование линзы, в которой сервис блокировки приложения хранит своё персистентное состояние.
Тем временем пазл собрался:
• Никаких мьютексов в прикладном коде
• Вся потокобезопасность в инфраструктуре (ReactiveCocoa, SQLite, LMDB, POSLens)
• Общение одношедулерных сервисов – прямые вызовы
• Общение разношедулерных сервисов – косвенные вызовы & shared database.
Что приятно, все ветки метро функционируют изолированно и каждая может себе позволить передвигать данные с комфортной ей скоростью. Затор на одной ветке совершенно не влияет на другую.
Например, сложная логика автозагрузки более не аффектит на отзывчивость UI, ибо вся тяжесть бизнес-логики ложится только на ранлуп BL-потока.
Когда на ветке BL-шедулера на бешеных скоростях и в большом количестве перемещаются загрузки, UI, не торопясь, в первую очередь занимается своим делом (скролинг и анимации и вот это всё) лишь изредка, когда считает нужным, подсинкивая состояние загрузок.
Забавный факт. В единственной собственноручно написанной инфраструктурной библиотеке, ведающей потокобезопасной синхронизацией, у меня не обошлось без датарейсов. И это при крайне скурпулёзном написании кода по заветам уважаемого в нашем комьюнити автора.
Кому хочется еще больше про Schedulable Architecture, то не могу не предложить свою собственную статью на хабре. Она была написана, когда концепт существовал только на уровне идеи. Сейчас он в проде.
habr.com/ru/company/mai…
На этом всё. С удовольствием отвечу на ваши вопросы. Желаю вам однопоточных приложений и никогда не влипать в многопоточные истории. Но если уж такое случилось, то пользуйтесь проверенными годами решениями.
Missing some Tweet in this thread?
You can try to force a refresh.

Like this thread? Get email updates or save it to PDF!

Subscribe to Мобильный разработчик
Profile picture

Get real-time email alerts when new unrolls are available from this author!

This content may be removed anytime!

Twitter may remove this content at anytime, convert it as a PDF, save and print for later use!

Try unrolling a thread yourself!

how to unroll video

1) Follow Thread Reader App on Twitter so you can easily mention us!

2) Go to a Twitter thread (series of Tweets by the same owner) and mention us with a keyword "unroll" @threadreaderapp unroll

You can practice here first or read more on our help page!

Follow Us on Twitter!

Did Thread Reader help you today?

Support us! We are indie developers!


This site is made by just three indie developers on a laptop doing marketing, support and development! Read more about the story.

Become a Premium Member ($3.00/month or $30.00/year) and get exclusive features!

Become Premium

Too expensive? Make a small donation by buying us coffee ($5) or help with server cost ($10)

Donate via Paypal Become our Patreon

Thank you for your support!