{
    "version": "https:\/\/jsonfeed.org\/version\/1",
    "title": "\/\/ by kei_sidorov: posts tagged многопоточность",
    "_rss_description": "Личный блог для статеек",
    "_rss_language": "en",
    "_itunes_email": "",
    "_itunes_categories_xml": "",
    "_itunes_image": "",
    "_itunes_explicit": "",
    "home_page_url": "https:\/\/sidorov.tech\/tags\/mnogopotochnost\/",
    "feed_url": "https:\/\/sidorov.tech\/tags\/mnogopotochnost\/json\/",
    "icon": "https:\/\/sidorov.tech\/user\/userpic@2x.jpg?1729508804",
    "author": {
        "name": "Кирилл Сидоров",
        "url": "https:\/\/sidorov.tech\/",
        "avatar": "https:\/\/sidorov.tech\/user\/userpic@2x.jpg?1729508804"
    },
    "items": [
        {
            "id": "4",
            "url": "https:\/\/sidorov.tech\/all\/ustroystvo-mnogopotochnosti-v-ios\/",
            "title": "Устройство многопоточности в iOS",
            "content_html": "<p>В 2000 году Apple выпустила открытую unix-like ОС <a href=\"https:\/\/ru.wikipedia.org\/wiki\/Darwin\">Darwin<\/a>, которая уже в следующем году послужит базой для первой версии Mac OS X — 10.0,  которая, в свою очередь, в будущем станет прародителем всех операционных систем Apple, начиная от современных macOS и iOS, заканчивая watchOS в часах и audioOS в «умных» колонках.<\/p>\n<p><cut\/><\/p>\n<div class=\"callout\"><div class=\"pin\"><p>🎓<\/p>\n<\/div><p>Эта статья написана по мотивам сессии «<a href=\"https:\/\/youtube.com\/watch?v=GVXyrLB1tbk\">Устройство многопоточности в iOS<\/a>» проходившей в рамках третьего сезона <a href=\"https:\/\/podlodka.io\/crew\">Podlodka iOS Crew<\/a>, спикером которой был <a href=\"https:\/\/twitter.com\/looneyconey\">Александр Андрюхин<\/a>. Я позволили себе изменить последовательность повествования, а так же более полно раскрыть некоторые темы, а некоторые вовсе убрать.<\/p>\n<p>В любом случае, статья не будет заменой видео, а видео — статье. <a href=\"https:\/\/podlodka.io\/ioscrew\">Залетайте на остаток сезона<\/a> — впереди целая неделя живых докладов, посвященых скиллам, которые нужны, чтобы сделать из обычного приложения крутой продукт! Приятным бонусом будет доступ к видео с сессиями первой недели про многопоточность.<\/p>\n<\/div><p><br \/><\/p>\n<p>Darwin построен на <a href=\"https:\/\/ru.wikipedia.org\/wiki\/XNU\">XNU<\/a> — гибридном ядре, включающим в себя микроядро <a href=\"https:\/\/ru.wikipedia.org\/wiki\/Mach\">Mach<\/a> и некоторые части ОС семейства BSD. В контексте этой заметке нам важно что Darwin получил от BSD модель процессов unix и модель тредов <a href=\"https:\/\/ru.wikipedia.org\/wiki\/POSIX\">POSIX<\/a>, а от Mach — слегка переосмысленное предствление процессов как задач.<\/p>\n<p>Для работы с потоками ОС разработчикам доступна С библиотека <a href=\"https:\/\/ru.wikipedia.org\/wiki\/POSIX_Threads\">pthread<\/a> — первый слой абстракции в наших операционных системах. Несмотря на то, что использовать её в своём коде можно и по сей день, Apple никогда не рекомендовала использовать pthread напрямую. Уже с первых версий версий Mac OS, разработчикам была доступна абстракция Apple поверх phread — <a href=\"https:\/\/developer.apple.com\/documentation\/foundation\/thread\">NSThread<\/a>.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/sidorov.tech\/pictures\/Runloop@2x.png\" width=\"765\" height=\"333\" alt=\"\" \/>\n<\/div>\n<p>NSThread — второй слой абстракции, которая нам заботливо предоставила Apple. Помимо более привычного Objective-C синтаксиса для создания и управления потоками, корпорация предоставила <a href=\"https:\/\/developer.apple.com\/documentation\/foundation\/runloop\">RunLoop<\/a> — цикл обслуживания задач и событий. RunLoop использует инструменты микроядра Mach (порты, XPC, ивенты и задачи) для управления потоком POSIX и может перевести поток в режим сна, если ему нечего делать и пробудить, когда появиться работа.<\/p>\n<p>Конечно, пользоваться NSThread можно и по сей день, но стоит помнить, что создание потока — дорогая операция, т.к. сам поток, мы должны запросить у самой ОС. Кроме того, синхронизация потоков и доступа к ресурсам несколько неудобна для повседневной разработки, поэтому разработчики Apple задумались о решении проблемы удобства работы с асинхронным кодом.<\/p>\n<h2>Grand Central Dispatch<\/h2>\n<p>С выходом iOS 4, Apple подняла разработчиков выше еще на один уровень абстракции и представила <a href=\"https:\/\/developer.apple.com\/documentation\/DISPATCH\">Grand Central Dispatch<\/a> и вводит понятие очередей и задач для организации асинхронного кода. GCD — это высокоуровневый API, позволяющее создавать пользовательские очереди, управлять задачами в них, решать вопросы синхронизации и делать это максимально эффективно.<\/p>\n<p>Т.к. очереди — это всего лишь слой абстракции, под капотом они используют всё те же системные треды, но механизм их создания и использования оптимизирован. CGD имеет пул предсозданных потоков и распределяет задачи эффективно, максимально утилизируя процессор если это необходимо. Разработчикам более не нужно думать о самих потоках, их создании и управлении.<\/p>\n<p>Помимо создании новой очереди вручную, GCD предоставляет доступ к главной очереди, на которой работает UI и доступ к нескольким системным (глобальным) очередям.<\/p>\n<p>Очереди GCD бывают двух типов:<\/p>\n<ul>\n<li>serial — последовательные<\/li>\n<li>concurrent — параллельные<\/li>\n<\/ul>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/sidorov.tech\/pictures\/Concurent@2x.png\" width=\"809\" height=\"393\" alt=\"\" \/>\n<\/div>\n<p>Не сложно догадаться, что на serial очереди задачи будут выполняться последовательно, друг за другом, а на параллельной будут выполнятся одновременно. По умолчанию очередь создаётся с последовательными выполнением задач, а чтобы создать concurrent очередь, необходимо явно это указать<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">let queue = DispatchQueue(&quot;com.company.name.app&quot;, attributes: .concurrent)<\/code><\/pre><p>Как уже было сказано, GCD предоставляет уже созданные, глобальные очереди, которые отличаются приоритетом:<\/p>\n<ul>\n<li><samp>global(qos: .userInteractive)<\/samp> — Для заданий, которые взаимодействуют с пользователем в данный момент и занимают очень мало времени.<\/li>\n<li><samp>global(qos: .userInitiated)<\/samp> — Для заданий, которые инициируются пользователем и требуют обратной связи.<\/li>\n<li><samp>global(qos: .utility)<\/samp> — Для заданий, которые требуют некоторого времени для выполнения и не требуют немедленной обратной связи.<\/li>\n<li><samp>global(qos: .background)<\/samp> — Для заданий, не связанных с визуализацией и не критичных ко времени исполнения.<\/li>\n<\/ul>\n<p>⚠️ Все глобальные очереди — очереди с параллельным выполнением задач.<\/p>\n<h2>Постановка задачи в очередь<\/h2>\n<p>Задачи в любою очередь, параллельную или последовательную, могут быть поставлены синхронно и асинхронно. При асинхронной постановке задачи в очередь, код, следующий за постановкой задачи в очередь, продолжит выполняться.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/sidorov.tech\/pictures\/Async@2x.png\" width=\"809\" height=\"478\" alt=\"\" \/>\n<\/div>\n<pre class=\"e2-text-code\"><code class=\"\">...\r\nDispatchQueue.global().async {\r\n   processImage()\r\n}\r\ndoRequest() \/\/ выполнется сразу, не дожидаясь processImage()<\/code><\/pre><p>А в случае с синхронной постановкой, код, следующей за ней, не продолжит своё выполнение, пока не будет выполнена поставленная в очередь задача.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">...\r\nDispatchQueue.global().sync {\r\n   processImage()\r\n}\r\ndoRequest() \/\/ будет ждать processImage()<\/code><\/pre><h2>DispatchWorkItem<\/h2>\n<p><a href=\"https:\/\/developer.apple.com\/documentation\/dispatch\/dispatchworkitem\">DispatchWorkItem<\/a> — специальный класс GCD, более объектно-ориентированную альтернативу замыканию (блоку) для постановки задачи в очередь. В отличии от обычной постановки задачи в очередь, DispatchWorkItem имеет возможность:<\/p>\n<ul>\n<li>указать приоритет задачи<\/li>\n<li>получить уведомление о завершении задачи<\/li>\n<li>отменить задачу<\/li>\n<\/ul>\n<div class=\"callout\"><div class=\"pin\"><p>☝️<\/p>\n<\/div><p>Важно понимать, что отмена задания работает до момента старта задачи, то есть пока она находиться в очереди. Если GCD начал исполнять код в блоке DispatchWorkItem отмена задания не приведет ни к какому результату, код продолжит своё выполнение.<\/p>\n<\/div><h2>NSOperation<\/h2>\n<p>GCD представляет удобную абстракцию для написания асинхронного кода, как с точки зрения идеи, так и с точки зрения синтаксиса. Но так было не всегда. В Objective-C (да и в первых версиях Swift) оперирование очередями и задачами было не таким удобным как в современном Swift да и вообще шло вразрез самому названию языка программирования. Apple необходимо было предоставить объектно ориентированную альтернативу GCD, и она это сделала представив <a href=\"https:\/\/developer.apple.com\/documentation\/foundation\/operation\">NSOperation<\/a>.<\/p>\n<p>NSOperations — это по сути те же очереди, вместо DispatchQueue здесь OperationQueue, а вместо DispatchWorkItem — Operation. Но помимо ООПшного синтаксиса API Operations предоставляет два крутых перимущества:<\/p>\n<ul>\n<li>Возможность указывать максимальное кол-во выполняемых одновременно задач в очереди.<\/li>\n<li>Указывать зависимые операции, выстраивая таким образом иерархию операций. В таком случае операция пойдет на выполнение только тогда, когда все операции, на которых она зависит, завершатся.<\/li>\n<\/ul>\n<p>Последний пункт очень удобен для построения цепочки запросов, когда для выполнения одного запроса нужна информация от нескольких других.<\/p>\n<pre class=\"e2-text-code\"><code class=\"\">class CustomOperation: Operation {\r\n    var outputValue: Int?\r\n\r\n    var inputValue: Int {\r\n        return dependencies\r\n            .filter({ $0 is CustomOperation })\r\n            .first as? CustomOperation\r\n            .outputValue ?? 0\r\n    }\r\n    ...\r\n}\r\n\r\nlet operation1 = CustomOperation()\r\noperation1.start()\r\n\r\nlet operation2 = CustomOperation()\r\noperation2.addDependency(operation1)\r\noperation2.start()<\/code><\/pre><h2>Ошибки и проблемы<\/h2>\n<p>Говоря об асинхронности и многопоточности, нельзя обойти тему основных основных ошибок, которые разрабочики могут совершить при написании кода, выполняющегося параллельно.<\/p>\n<h2>Race Condition или состояние гонки<\/h2>\n<p>Race Condition или состояние гонки — это ошибка проектирования многопоточных систем, при которой доступ к ресурсу не синхронизирован и результат выполнения может зависеть от последовательности выполнения кода. Определение сложно понять без примера, поэтому лучше разобраться опираясь на какой-то пример.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/sidorov.tech\/pictures\/Racecodition@2x.png\" width=\"809\" height=\"296\" alt=\"\" \/>\n<\/div>\n<p>На иллюстрации изображено два потока, в которых происходит увеличение какого-то кода. Чтобы инкрементировать значение переменной, процессору нужно будет совершить три действия:<\/p>\n<ol start=\"1\">\n<li>считать значение переменной из общей памяти в регистр<\/li>\n<li>увеличить значение в регистре на 1<\/li>\n<li>записать значение из регистра обратно в общую память<\/li>\n<\/ol>\n<p>Как можно заметить на иллюстрации, считывание значения переменной вторым потом происходит до того, как первый поток успел записать увеличенное значение. В итоге теряется одно увеличение счетчика что может быть как причиной безобидного бага, так и серьезной проблемы приводящей к аварийному завершению.<\/p>\n<div class=\"callout\"><div class=\"pin\"><p>💡<\/p>\n<\/div><p>Самым серьёзным последствием Race Condition считается кейс ПО медицинсокого аппарата для лучевой терапии <a href=\"https:\/\/ru.wikipedia.org\/wiki\/Therac-25\">Therac-25<\/a>. Состояние гонки приводило к неправильным значениям в переменой, используемой для определения режима работы лучевого механизма.<\/p>\n<\/div><h2>Deadlock<\/h2>\n<p>Deadlock или взаимная блокировка — ошибка многопоточного ПО, при которой несколько потоков могут взаимно ожидать освобождения некоторого ресурса бесконечное время.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/sidorov.tech\/pictures\/Deadlock@2x.png\" width=\"809\" height=\"311\" alt=\"\" \/>\n<\/div>\n<p>Разберем на примере. Допустим задача, в потоке А блокирует доступ к некому ресурсу А, пусть это будет некий SettingsStorage. Задача А блокирует доступ к стораджу, читает оттуда некоторые значения, и что-то вычисляет. В это время стартует задача B и блокирует доступ к ресурсу B, пусть это будет база данных. Чтобы выполнить некоторые вычисления задаче В тоже потребовался доступ к SettingsStorage и задача начинает ждать, когда задача А его освободит. В это время, задаче А понадобился доступ к БД, но он уже заблокирован задачей В. Происходит взаимна блокировка: задача А ожидает БД, которая заблокирована задачей В, которая ожидает сторадж, заблокированный задачей А.<\/p>\n<h2>Инверсия приоритетов<\/h2>\n<p>Инверсия приоритетов — ошибка, приводящая к смене приоритетов у потоков, которой не предполагался разработчиком.<\/p>\n<div class=\"e2-text-picture\">\n<img src=\"https:\/\/sidorov.tech\/pictures\/PriorityInversion@2x.png\" width=\"809\" height=\"311\" alt=\"\" \/>\n<\/div>\n<p>Пусть у нас есть всего две задачи с разным приоритетом и всего 1 ресурс, пусть это снова будет БД. Первым в очередь помещается задача с низким приоритетом. Она выполняет свою работу и в момент времени Т1 ей понадобилась БД и она блокирует доступ к ней. Почти сразу же после этого, стартует высокоприоритетная задача и вытесняет низкоприоритетную. Все идет по плану до момента Т3, где высокоприоритетная задача пытается завладеть БД. Так как ресурс заблокирован, задача высокоприоритетная задача переводится в ожидание, а низкоприоритетная получает процессорное время. Временной промежуток T3-T4 называют ограниченной инверсией приоритетов. В этом промежутке наблюдается логическое несоответствие с правилами планирования — задача с более высоким приоритетом находится в ожидании в то время как низкоприоритетная задача выполняется.<\/p>\n<div class=\"e2-text-video\">\n<iframe src=\"https:\/\/www.youtube.com\/embed\/GVXyrLB1tbk\" frameborder=\"0\" allowfullscreen><\/iframe><\/div>\n",
            "date_published": "2020-11-16T11:23:29+00:00",
            "date_modified": "2020-11-18T04:18:05+00:00",
            "image": "https:\/\/sidorov.tech\/pictures\/Runloop@2x.png",
            "_date_published_rfc2822": "Mon, 16 Nov 2020 11:23:29 +0000",
            "_rss_guid_is_permalink": "false",
            "_rss_guid": "4",
            "_e2_data": {
                "is_favourite": false,
                "links_required": [
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css",
                    "system\/library\/highlight\/highlight.js",
                    "system\/library\/highlight\/highlight.css"
                ],
                "og_images": [
                    "https:\/\/sidorov.tech\/pictures\/Runloop@2x.png",
                    "https:\/\/sidorov.tech\/pictures\/Concurent@2x.png",
                    "https:\/\/sidorov.tech\/pictures\/Async@2x.png",
                    "https:\/\/sidorov.tech\/pictures\/Racecodition@2x.png",
                    "https:\/\/sidorov.tech\/pictures\/Deadlock@2x.png",
                    "https:\/\/sidorov.tech\/pictures\/PriorityInversion@2x.png",
                    "https:\/\/sidorov.tech\/pictures\/remote\/youtube-GVXyrLB1tbk-cover.jpg"
                ]
            }
        }
    ],
    "_e2_version": 3576,
    "_e2_ua_string": "E2 (v3576; Aegea)"
}