Багатопоточність (concurrency) в Swift 3. GCD і Dispatch Queues

Треба сказати, що многопоточность (сопсиггепсу) в iOS завжди входить до питання, що задаються на інтерв'ю розробників iOS додатків, а також в число топ помилок, які роблять програмісти при розробці iOS додатків. Тому так важливо володіти цим інструментом досконало.
Отже, у вас є додаток, воно працює на
main thread
(головному потоці), який відповідає за виконання коду, що відображає ваш користувальницький інтерфейс (
UI
). Як тільки ви починаєте додавати до вашого додатком такі «затратні за часом» шматки коду, як завантаження даних з мережі або обробка зображень на
main thread
(головному потоці), то робота вашого
UI
починає сильно сповільнюватися і навіть може призвести до повного його «заморожування».



Як можна змінити архітектуру програми, щоб таких проблем не виникало? У цьому випадку на допомогу приходить багатопоточність (
сопсиггепсу
), яка дозволяє одночасно виконувати дві або більш незалежні задачі (
завдання
): обчислення, завантаження даних з мережі або з диска, обробку зображень і т. д.

Процесор в кожний заданий момент часу може виконувати одну з ваших завдань і для неї виділяється відповідний потік (
thread
).
У разі одноядерного процесора (iPhone і iPad), багатопоточність (
сопсиггепсу
) досягається багаторазовими короткочасними перемиканнями між «потоками» (
threads
), на яких виконуються завдання (
завдання
), створюючи достовірне уявлення про одночасному виконанні завдань на одноядерному процесорі. На багатоядерний процесор (Mac) багатопоточність досягається тим, що кожному «потоку», пов'язаній із завданням, надається своє власне ядро для запуску завдань. Обидві ці технології використовують загальне поняття багатопоточності (
сопсиггепсу
).

Своєрідною платою за введення багатопоточності у вашому приложениии є труднощі забезпечення безпечного виконання коду на різних потоках (
thread safety
). Як тільки ми дозволяємо завдань (
завдання
) працювати паралельно, з'являються проблеми, пов'язані з тим, що різні завдання (
завдання
) захочуть отримати доступ до одних і тих же ресурсів, наприклад, захочуть змінювати одну і ту ж змінну в різних потоках, чи захочуть отримати доступ до ресурсів, які вже заблоковані іншими завданнями. Це може призвести до руйнування самих ресурсів, що використовуються завданнями на інших потоках.

У iOS програмуванні багатопоточність надається розробникам у вигляді декількох інструментів: Thread, Grand Central Dispatch (скорочено GCD) і Operation — і використовується з метою збільшення продуктивності і чуйності користувальницького інтерфейсу. Ми не будемо розглядати
Thread
, так як це низькорівневий механізм, а зосередимося на
GCD
в цій статті та
Operation
(об'єктно-орієнтованому
API
, побудованому поверх
GCD
) подальшої публікації.

Треба сказати, що до появи
Swift 3
настільки потужний фреймворк, як
Grand Central Dispatch (GCD
), мав
API
, засноване на мові С, яке на перший погляд здається просто книгою заклинань, і не відразу зрозуміло, як мобілізувати його можливості для виконання корисних для користувача завдань.
В
Swift 3
все кардинально змінилося.
GCD
отримав новий, повністю
Swift
-подібний синтаксис, який дуже легко використовувати. Якщо ви хоча б трохи знайомі зі старим
API GCD
, то весь новий синтаксис здасться вам просто легкою прогулянкою; якщо немає — то вам просто доведеться вивчити ще один звичайний розділ програмування на
iOS
. Новий фреймворк
GCD
працює в
Swift 3 
на всіх
Apple
пристроях, починаючи від
Apple Watch
, включаючи всі
iOS
прилади, і кінчаючи
Apple TV
та
Mac
.

Ще одна хороша новина полягає в тому, що починаючи з
Xcode 8
, можна використовувати для вивчення
GCD
та
Operation
такий потужний і наочний інструмент, як
Playgroud
. В
Xcode 8
з'явився новий допоміжний клас
<font color="#0000FF">PlaygroudPage</font>
, у якого є функція, що дозволяє
Playgroud
жити необмежений час. У цьому випадку чергу
<font color="#0000FF">DispatchQueue</font>
може працювати до тих пір, поки робота не закінчиться. Це особливо важливо для мережевих запитів. Для того, щоб використовувати клас
<font color="#0000FF">PlaygroudPage</font>
, вам потрібно імпортувати модуль
<font color="#0000FF">PlaygroudSupport</font>
. Цей модуль також дозволяє отримати доступ до циклу виконання (
run loop
), відображати «живий»
UI
, а також виконувати асинхронні операції на
Playgroud
. Нижче ми побачимо, як виглядає ця настройка в роботі. Ця нова можливість
Playground
на
Xcode 8
робить вивчення багатопоточності в
Swift 3
дуже простим і наочним.

Для кращого розуміння багатопоточності (
concurrency
),
Apple
ввела деякі абстрактні поняття, з якими оперують обидва інструменту —
GCD
та
Operation
. Основним поняттям є черга (
queue
). Тому, коли ми говоримо про багатопоточності в
iOS
з точки зору розробника
iOS
додатків, ми говоримо про черги (
queues
). Черги (
queues
) — це звичайні черги, які шикуються люди, щоб купити, наприклад, квиток в кінотеатр, але в нашому випадку в чергу шикуються замикання (
closure 
— анонімні блоки коду). Система просто виконує їх згідно черги, «висмикуючи» наступного по черзі і запускаючи його на виконання у відповідному цій черзі потоці. Черги (
queues
) слідують
FIFO
паттерну (
First In, First Out
), це означає, що той, хто першим був поставлений в чергу, буде першим спрямований на виконання. У вас може бути безліч черг (
queues
) та система «висмикує» замикання по одному з кожної черги і запускає їх на виконання їх власних потоках. Таким чином, ви отримуєте багатопоточність.

Але це лише загальне уявлення про те, як багатопоточність (
сопсиггепсу
) працює в
iOS
. Інтрига полягає в тому, що собою являють ці черги в сенсі виконання завдань по відношенню один до одного (послідовне або паралельне) і з допомогою якої функції (синхронної або асинхронної) ці завдання ставляться в чергу, тим самим блокуючи або не блокуючи поточну чергу.

Послідовні (
serial
) і паралельні (
concurrent
) черги
Черги (
queues
) можуть бути "
serial
" (послідовними), коли задача (замикання), яка знаходиться на вершині черги, «витягується» iOS і працює до тих пір, поки не закінчиться, потім витягується наступний елемент з черги і т. д. Це
serial queue 
або послідовна чергу. Черги (
queues
) можуть бути «c
oncurrent
» (багато), коли система «витягує» замикання, що знаходиться на вершині черги, і запускає її на виконання в певному потоці. Якщо у системи ще є ресурси, то вона бере наступний елемент з черги і запускає його на виконання в іншому потоці в той час, поки перша функція ще працює. І так система може витягнути цілий ряд функцій. Для того, щоб не плутати загальне поняття багатопоточності з
"concurrent queues"
(багато чергами), ми будемо називати
"concurrent queue"
паралельної чергою, маючи на увазі порядок виконання завдань на ній по відношенню один до одного, не вдаючись в технічну реалізацію цієї паралельності.



Ми бачимо, що на
serial
(послідовної) черги завершення замикань відбувається строго в тому порядку, в якому вони надходили на виконання, в той час як на
concurrent
(паралельної) черги завдання закінчуються непередбачуваним чином. Крім того, ви бачите, що загальний час виконання певної групи завдань на
serial
черги значно перевершує час виконання тієї ж групи завдань на
concurrent
черги. На
serial
(послідовної) черги в будь-поточний момент часу виконується тільки одне завдання, а
concurrent
(паралельної) черги кількість завдань у будь-поточний момент часу може змінюватися.

Синхронне і асинхронне виконання завдань
Як тільки черга (
queue
) створена, завдання на ній можна розмістити за допомогою двох функцій:
<font color="#0000FF">sync</font>
— синхронне виконання по відношенню до поточної черги і
<font color="#0000FF">async</font>
— асинхронне виконання по відношенню до поточної черги.

Синхронна функція
<font color="#0000FF">sync</font>
повертає керування на поточний тільки після повного завершення завдання, тим самим блокуючи поточну чергу:



Асинхронна функція
<font color="#0000FF">async</font>
, в протилежність функції
<font color="#0000FF">sync</font>
повертає керування на поточний чергу негайно після запуску завдання на виконання в іншій черзі, не чекаючи його закінчення. Таким чином, асинхронна функція
<font color="#0000FF">async</font>
не блокує виконання завдань поточної черги:



«Інший чергою» може опинитися в разі асинхронного виконання як послідовна (
serial
) чергу:



так і паралельна (
concurrent
) чергу:



Завдання розробника полягає лише у виборі черги і додаванні завдання (як правило, замикання) в цю чергу синхронно з допомогою функції
<font color="#0000FF">sync</font>
або асинхронно за допомогою функції
<font color="#0000FF">async</font>
, далі працює виключно
iOS
.

Повертаючись до задачі, представленої на самому початку цього посту, ми перемкнемо виконання завдання отримання даних з мережі «Data Network from» на іншу чергу:



Після отримання даних
<font color="#0000FF">Data</font>
з мережі на іншій черзі
<font color="#0000FF">Dispatch Queue</font>
, ми посилаємо їх назад на
<b>Main thread</b>
.



Коли ми отримуємо дані
<font color="#0000FF">Data</font>
з мережі на іншій черзі
<font color="#0000FF">DispatchQueue</font>
,
<b>Main thread</b>
— вільна і обслуговує всі події, які відбуваються на
UI
. Давайте подивимося, як виглядає реальний код для цього випадку:



Для виконання завантаження даних URL-адресою
<font color="#0000FF">imageURL</font>
, що може зайняти значний час і заблокувати
<b>Main queue</b>
, ми АСИНХРОННО перемикаємо виконання цього ресурсу-ємного завдання на глобальну паралельну чергу з якістю обслуговування
<font color="#0000FF">qos</font>
, рівним
<font color="#0000FF">.utility</font>
(більш детально про це трохи пізніше):

let imageURL: URL = URL(string: "http://www.planetware.com/photos-large/F/france-paris-eiffel-tower.jpg")!
let queue = DispatchQueue.global(qos: .utility)
queue.async{
if let data = try? Data(contentsOf: imageURL){
DispatchQueue.main.async {
image.image = UIImage(data: data)
print("Show image data")
}
print("Did download image data")
}
}


Після отримання даних
<font color="#0000FF">data</font>
ми знову повертаємося на
<b>Main queue</b>
, щоб оновити наш
UI
елемент
<font color="#0000FF">image1.image</font>
з допомогою цих даних.
Ви бачите, як просто виконати ланцюжок перемикань на іншу чергу, щоб «відвести» виконання «витратних» завдань з
<b>Main queue</b>
, а потім знову до неї повернутися. Код знаходиться на EnvironmentPlayground.playground на Github.

Зауважте, що перемикання витратних завдань з
<b>Main queue</b>
на інший потік завжди АСИНХРОННО.
Потрібно бути дуже уважним з методом
<font color="#0000FF">sync</font>
для черг, тому що «поточний потік» вимушений чекати закінчення виконання завдання на іншій черзі. НІКОЛИ НЕ викликайте метод
<font color="#0000FF">sync</font>
на
<b>Main queue</b>
, тому що це призведе до
deadlock
програми
! (про це нижче)

Глобальні черзі
Крім черг, які потрібно спеціально створювати, система
iOS
надає в розпорядження розробника готові (
out-of-the-box
) глобальні черги (
queues
). Їх 5:

1.) послідовна чергу
Main queue
, в якій відбуваються всі операції з призначеним для користувача інтерфейсом (
UI
):
let main = DispatchQueue.main

Якщо ви хочете виконати функцію або замикання, які щось роблять з призначеним для користувача інтерфейсом (
UI
),
<font color="#0000FF">UIButton</font>
або
<font color="#0000FF">UI-небудь</font>
, ви повинні помістити цю функцію або замикання на
Main queue
. Ця чергу має найвищий пріоритет серед глобальних черг.

2.) 4 фонових
concurrent
(паралельних) глобальних черзі з різною якістю обслуговування
<font color="#0000FF">qos</font>
і, звичайно, різними пріоритетами:
// найвищий пріоритет
let userInteractiveQueue = DispatchQueue.global(qos: .userInteractive)

let userInitiatedQueue = DispatchQueue.global(qos: .userInitiated)

let utilityQueue = DispatchQueue.global(qos: .utility)

// найнижчий пріоритет
let backgroundQueue = DispatchQueue.global(.background) 

// за замовчуванням 
let defaultQueue = DispatchQueue.global()

Кожну з цих черг
Apple
нагородила абстрактним «якістю обслуговування»
<font color="#0000FF">qos</font>
(скорочення для
Quality of Service
), і ми повинні вирішити, яким воно має бути для наших завдань.

Нижче представлені різні
<font color="#0000FF">qos</font>
і пояснюється, для чого вони призначені:

  • <font color="#0000FF">.userInteractive</font>
    — для завдань, які не взаємодіють з користувачем в даний момент і займають дуже мало часу: анімація, виконуються миттєво; користувач не хоче цього робити
    Main queue
    , однак це повинно бути зроблено по можливості швидко, так як користувач взаємодіє зі мною прямо зараз. Можна уявити ситуацію, коли користувач водить пальцем по екрану, а вам необхідно прорахувати щось, пов'язане з інтенсивною обробкою зображення, та ви розміщуєте розрахунок у цій черзі. Користувач продовжує водити пальцем по екрану, він не відразу бачить результат, результат трохи відстає від положення пальця на екрані, так як розрахунки вимагають деякого часу, але принаймні
    Main queue
    все ще «слухає» наші пальці і реагує на них. Ця чергу має дуже високий пріоритет, але нижче, ніж у
    Main queue
    .
  • <font color="#0000FF">.userInitiated</font>
    — для завдань, які ініціюються користувачем і вимагають зворотного зв'язку, але це не всередині інтерактивного події, користувач очікує зворотного зв'язку, щоб продовжити взаємодію; може зайняти кілька секунд; має високий пріоритет, але нижче, ніж у попередньої черги,
  • <font color="#0000FF">.utulity</font>
    — для завдань, які вимагають деякого часу для виконання і не вимагають негайного зворотного зв'язку, наприклад, завантаження даних або очищення деякої бази даних. Робиться щось, про що користувач не просить, але це необхідно для даного додатка. Завдання може зайняти від декількох секунд до декількох хвилин; пріоритет нижче, ніж у попередньої черги,
  • <font color="#0000FF">.background</font>
    — для завдань, не пов'язаних з візуалізацією і не критичних до часу виконання; наприклад,
    backups
    або синхронізація з
    web 
    — сервісом. Це те, що зазвичай запускається у фоновому режимі, відбувається тільки тоді, коли ніхто не хоче ніякого обслуговування. Просто фонове завдання, яка займає значний час від хвилин до годин; має найнижчий пріоритет серед усіх глобальних черг.
Є ще Глобальна паралельна (
concurrency
) чергу за замовчуванням
<font color="#0000FF">.default</font>
, яка повідомляє про відсутність інформації про «якості обслуговування»
<font color="#0000FF">qos</font>
. Вона створюється з допомогою оператора:
DispatchQueue.global()

Якщо вдається визначити
<font color="#0000FF">qos</font>
інформацію з інших джерел, то вона використовується, якщо немає, то використовується
<font color="#0000FF">qos</font>
між
<font color="#0000FF">.userInitiated</font>
та
<font color="#0000FF">.utility</font>
.

Важливо розуміти, що всі ці глобальні черги є СИСТЕМНИМИ глобальними чергами і наші завдання — не єдині завдання в цій черзі! Також важливо знати, що всі глобальні черги, крім одного, є
concurrent
(паралельними) чергами.

Особлива Глобальна послідовна чергу для користувацького інтерфейсу — Main queue
Apple забезпечує нас єдиною ГЛОБАЛЬНОЮ
serial
(ПОСЛІДОВНОЇ) чергою — це згадана вище
Main queue
. На цій черзі небажано виконувати ресурсомісткі операції (наприклад, завантаження даних з мережі), не пов'язані з зміні
UI
, щоб не «заморожувати»
UI
на час виконання цієї операції і зберегти чуйність користувальницького інтерфейсу на дії користувача в будь-який момент часу, наприклад, на жести.



Настійно рекомендується «відводити» такі ресурсомісткі операції на інші потоки або черги:



Є і ще одна жорстка вимога — ТІЛЬКИ на
Main queue
ми можемо змінювати
UI
елементи.

Це тому, що ми хочемо, щоб
Main queue
була не тільки «чуйною» на дії з
UI
(так, це основна причина), але ми хочемо також, щоб інтерфейс був захищений від «разлаживания» в многопоточной середовищі, тобто реакція на дії користувача виконувалася б строго послідовно в впорядкованої манері. Якщо ми дозволимо нашим елементів
UI
виконувати свої дії в різних чергах, то може статися, що малювання буде відбуватися з різною швидкістю, і дії буде перетинатися, що призведе до повної непередбачуваності на екрані. Ми використовуємо
Main queue
як свого роду «точку синхронізації», в яку повертається кожен, хто хоче малювати на екрані.

Проблеми багатопоточності
Як тільки ми дозволяємо завдань (
завдання
) працювати паралельно, з'являються проблеми, пов'язані з тим, що різні завдання захочуть отримати доступ до одних і тих же ресурсів.
Три основні проблеми:

  • стан гонки (
    race condition
    ) — помилка проектування многопоточной системи або програми, при якій робота системи або програми залежить від того, в якому порядку виконуються частини коду
  • інверсія пріоритетів (
    priority inversion
    )
  • взаємне блокування (
    deadlock
    ) — ситуація в многопоточной системі, при якій декілька потоків перебувають у стані нескінченного очікування ресурсів, зайнятих самими цими потоками


Стан гонки (race condition)
Ми можемо відтворити найпростіший випадок
race condition
, якщо будемо змінювати змінну
<font color="#0000FF">value</font>
асинхронно на
private
черги, а показувати
<font color="#0000FF">value</font>
на поточному потоці:



У нас є звичайна змінна
<font color="#0000FF">value</font>
і звичайна функція
<font color="#0000FF">changeValue</font>
для її зміни, причому навмисне ми зробили за допомогою оператора
<font color="#0000FF">sleep(1)</font>
так, що зміна змінної value вимагає значного часу. Якщо ми будемо запускати функцію
<font color="#0000FF">changeValue</font>
АСИНХРОННО за допомогою
<font color="#0000FF">async</font>
, то перш ніж дійде справа до розміщення зміненого значення змінної
<font color="#0000FF">value</font>
, на поточному потоці мінлива
<font color="#0000FF">value</font>
може бути переустановлене в інше значення, це і є
race condition
. Цьому коду відповідає друк у вигляді:



і діаграма, на якій наочно видно явище під назвою "
race condition
":



Давайте замінимо метод
<font color="#0000FF">async</font>
на
<font color="#0000FF">sync</font>
:



І печатка, і результат змінилися:

<img

і діаграма, на якій відсутнє явище під назвою "
race condition
":



Ми бачимо, що хоча потрібно бути дуже уважним з методом
<font color="#0000FF">sync</font>
для черг, тому що «поточний потік» вимушений чекати закінчення виконання завдання на іншій черзі, метод
<font color="#0000FF">sync</font>
виявляється дуже корисним для того, щоб уникнути race conditions. Код для імітації явища "
race condition
" можна подивитися на firstPlayground.playground на Github. Пізніше ми покажемо справжні "
race condition
" при формуванні рядка з символів, одержуваних на різних потоках. Буде також запропоновано елегантний спосіб формування рядка з використанням «бар'єрів», що дозволить уникнути"
race conditions
" і зробити формовану рядок потокобезопасной.

Інверсія пріоритетів (priority inversion)
З блокуванням ресурсів тісно пов'язане поняття
інверсії пріоритетів
:



Припустимо в системі існують дві задачі з низьким (А) і високим (Б) пріоритетом. В момент часу T1 завдання (А) блокує ресурс і починає його обслуговувати. В момент часу T2 завдання (Б) витісняє низкоприоритетную завдання (А) і намагається заволодіти ресурсом в момент часу T3. Але так як ресурс заблоковано, завдання (Б) переводиться в очікування, а завдання (А) продовжує виконання. В момент часу Т4 завдання (А) завершує обслуговування ресурсу і розблокувати його. Так як ресурс очікує завдання (Б), вона тут же починає виконання.
Часовий проміжок (T4-T3) називають обмеженою інверсією пріоритетів. У цьому проміжку спостерігається логічне невідповідність з правилами планування — завдання з більш високим пріоритетом знаходиться в очікуванні в той час як низкоприоритетная завдання виконується.

Але це ще не найстрашніше. Припустимо в системі працюють три завдання: низкоприоритетная (А), з середніми пріоритетом (Б) і высокоприоритетная (В):



Якщо ресурс заблоковано завданням (А), а він потрібен задачі (В), то спостерігається та ж ситуація — высокоприоритетная завдання блокується. Але припустимо, що задача (Б) витіснила (А), після того, як (В) пішла в очікування ресурсу. Завдання (Б) нічого не знає про конфлікт, тому може виконуватися як завгодно довго на проміжку часу (T5-T4). Крім того, крім (Б) в системі можуть бути й інші завдання, з пріоритетами більше (А), але менше (Б). Тому тривалість періоду (T6-T3) в загальному випадку невизначена. Таку ситуацію називають необмеженої інверсією пріоритетів.

Обмеженою інверсії пріоритетів у загальному випадку уникнути неможливо, проте вона не так небезпечна для багатопотокового додатку, як необмежена. Усувається примусовим підвищенням пріоритетів всіх «заважають» завдань з низьким пріоритетом.

Нижче ми покажемо, як можна з допомогою
<font color="#0000FF">DispatchWorkItem</font>
об'єктів збільшувати пріоритет окремих завдань поточної черги.

Взаємне блокування (deadlock)
Взаємне блокування — це аварійний стан системи, яке може виникати при вкладеності блокувань ресурсів. Припустимо в системі існують дві задачі з низьким (А) і високим (Б) пріоритетом, які використовують два ресурсу — X і Y:



В момент часу T1 завдання (А) блокує ресурс X. Потім в момент часу T2 завдання (А) витісняє більш пріоритетне завдання (Б), яка в момент часу T3 блокує ресурс Y. Якщо задача (Б) спробує заблокувати ресурс X (T4) не звільнивши ресурс Y, то вона буде переведена у стан очікування, а виконання завдання (А) буде продовжено. Якщо в момент часу T5 завдання (А) спробує заблокувати ресурс Y, не звільнивши X, виникне стан взаємної блокування — ні одне із завдань (А) і (Б) не зможе отримати управління.

Взаємне блокування можлива тільки тоді, коли в системі використовується залежний (вкладений) багатопотоковий доступ до ресурсів. Взаємної блокування можна уникнути, якщо не використовувати вкладеність, або якщо ресурс використовує протокол збільшення пріоритету.
Якщо ми в задачі, представленої на початку посту, після отримання даних з мережі в фонової черги, спробуємо використати для повернення на main queue метод sync, ми отримаємо взаємне блокування (
deadock
).

НІКОЛИ НЕ викликайте метод
<font color="#0000FF">sync</font>
на
main queue
, тому що це призведе до взаємного блокування (
deadlock
) вашої програми!

Експериментальна середовище
Для експериментів ми будемо використовувати
Playground
, налаштовану на нескінченне час роботи c допомогою модуль
<font color="#0000FF">PlaygroundSupport</font>
і класу
<font color="#0000FF">PlaygroudPage</font>
, щоб ми змогли завершитися всі задачі, вміщені в черзі і отримати доступ до
main queue
. Ми можемо зупинити очікування якоїсь події на
Playground
c допомогою команди
<font color="#0000FF">PlaygroundPage.current.finishExecution()</font>
.
Є ще одна крута можливість на
Playground
— можливість взаємодії з «живим»
UI
з допомогою команди
PlaygroundPage.liveView = viewController

і Асистента Редактора (
Assistant Editor
). Якщо, наприклад, ви створюєте
<font color="#0000FF">viewController</font>
, то для того, щоб побачити ваш
<font color="#0000FF">viewController</font>
, вам достатньо налаштувати
Playground
на необмежену виконання коду та включити Асистента Редактора (
Assistant Editor
). Доведеться закоментувати команду
<font color="#0000FF">PlaygroundPage.current.finishExecution()</font>
і зупиняти
Playground
вручну.



Playground
c кодом шаблону експериментальної середовища має ім'я EnvironmentPlayground.playground і знаходиться на Github.

1. Перший експеримент. Глобальні черги і завдання
Почнемо з простих експериментів. Визначимо також ряд глобальних черг: одну послідовну
<font color="#0000FF">mainQueue</font>
— це
main queue
і чотири паралельні (
concurrent
)
queues
<font color="#0000FF">userInteractiveQueue</font>
,
<font color="#0000FF">userQueue</font>
,
<font color="#0000FF">utilityQueue</font>
та
<font color="#0000FF">backgroundQueue</font>
. Можна задати
concurrent queue
за замовчуванням —
<font color="#0000FF">defautQueue</font>
:



В якості завдання
<font color="#0000FF">task</font>
виберемо друк будь-яких десяти однакових символів і пріоритету поточної черги. Ще одне завдання
<font color="#0000FF">taskHIGH</font>
, яка буде друкувати один символ, ми будемо запускати з високим пріоритетом:



2. Другий експеримент буде стосуватися СИНХРОННОСТІ АСИНХРОННОСТІ на глобальних чергах
Як тільки ви отримали глобальну чергу, наприклад,
<font color="#0000FF">userQueue</font>
, ви можете виконувати завдання на ній або СИНХРОННО, використовуючи метод
<font color="#0000FF">sync</font>
, або АСИНХРОННО, використовуючи метод
<font color="#0000FF">async</font>
.



У випадку синхронного
<font color="#0000FF">sync</font>
виконання ми бачимо, що всі завдання стартують послідовно, один за іншим, і наступне чітко чекає завершення попереднього. Більш того, в якості оптимізації функція sync може запустити замикання на поточному потоці, якщо це можливо, і пріоритет глобальної черги не буде мати значення. Саме це ми і бачимо.

У разі ж асинхронного
<font color="#0000FF">async</font>
виконання, ми бачимо, що завдання

стартують, не чекаючи завершення завдань

і пріоритет глобальної черзі
<font color="#0000FF">userQueue</font>
вище пріоритету виконання коду на
Playground
. Отже, завдання на
<font color="#0000FF">userQueue</font>
виконуються частіше.

3. Третій експеримент. Private послідовні черзі
Крім глобальний черг ми можемо створювати користувальницькі
Private
черзі з допомогою ініціалізатор класу
<font color="#0000FF">DispatchQueue</font>
:



Єдине, що необхідно вказати настроювана черги, — це унікальна мітка
<font color="#0000FF">label</font>
, яку
Apple
рекомендує задавати у вигляді інверсної
DNS
нотації (
“com.bestkora.mySerial"
), саме під таким ім'ям буде видно чергу в налагоджувач. Тим не менш, це необов'язково, і ви можете використовувати будь-який рядок, лише б вона залишалася унікальною. Якщо ви не ставите більше ніяких інших аргументів окрім
<font color="#0000FF">label</font>
при ініціалізації
Private
черзі, то за замовчуванням створюється послідовна (
<font color="#0000FF">.serial</font>
) чергу. Є й інші аргументи, які можна задати при ініціалізації черзі, і про них ми поговоримо трохи пізніше.
Дивимося, як працює користувацька
Private
послідовна чергу
<font color="#0000FF">mySerialQueue</font>
при використанні
<font color="#0000FF">sync</font>
та
<font color="#0000FF">async</font>
методів:



У разі синхронного
<font color="#0000FF">sync</font>
ми бачимо ту ж ситуацію, що і в експерименті 3 -тип черги не має значення, тому що в якості оптимізації функція
<font color="#0000FF">sync</font>
може запустити замикання на поточному потоці. Саме це ми і бачимо.

Що станеться, якщо ми використовуємо
<font color="#0000FF">async</font>
метод і дозволимо послідовній черзі
<font color="#0000FF">mySerialQueue</font>
виконати завдання

асинхронно по відношенню до поточної черги? У цьому разі виконання програми не зупиняється і не чекає, поки завершиться це завдання у черзі
<font color="#0000FF">mySerialQueue</font>
; управління негайно перейде до виконання завдань

і буде виконувати їх в один і той же час, що і завдання


4. Четвертий експеримент буде стосуватися пріоритетів QoS послідовних черг
Давайте призначимо нашій
Private
послідовній черзі
<font color="#0000FF">serialPriorityQueue</font>
якість обслуговування qos, рівне .userInitiated, і поставимо асинхронно в цю чергу спочатку завдання

а потім

Цей експеримент переконає нас у тому, що наша нова черга
<font color="#0000FF">serialPriorityQueue</font>
дійсно є послідовною, і незважаючи на використання
<font color="#0000FF">async</font>
методу, завдання виконуються послідовно один за одним в порядку надходження:



Таким чином, для багатопоточного виконання коду недостатньо використовувати метод
<font color="#0000FF">async</font>
, потрібно мати багато потоків або за рахунок різних черг, або за рахунок того, що сама чергу є паралельною (
<font color="#0000FF">.concurrent</font>
). Нижче в експерименті 5 з паралельними (
<font color="#0000FF">.concurrent</font>
) чергами ми побачимо аналогічний експеримент з Private паралельної (
<font color="#0000FF">.concurrent</font>
) чергою
<font color="#0000FF">workerQueue</font>
, але там буде зовсім інша картина, коли ми будемо розміщати в цю чергу ті ж самі завдання.

Давайте використаємо послідовні
Private
черзі з різними пріоритетами для асинхронної постановки в цю черзі спочатку завдань

, а потім завдань


чергу
<font color="#0000FF">serialPriorityQueue1</font>
c
<font color="#0000FF">qos .userInitiated</font>

чергу
<font color="#0000FF">serialPriorityQueue2</font>
c
<font color="#0000FF">qos .background</font>




Тут відбувається багатопоточне виконання завдань, і вправи частіше виконуються на черзі
<font color="#0000FF">serialPriorityQueue1</font>
, що має більш пріоритетне якість обслуговування
<font color="#0000FF">qos: .userIniatated</font>
.

Ви можете затримати виконання завдань на будь черзі
<font color="#0000FF">DispatchQueue</font>
на заданий час, наприклад, на
<font color="#0000FF">now() + 0.1</font>
з допомогою функції
<font color="#0000FF">asyncAfter</font>
та ще змінити при цьому якість обслуговування
<font color="#0000FF">qos</font>
:



5. П'ятий експеримент буде стосуватися Private паралельних (concurrent) черг
Для того, щоб ініціалізувати
Private
паралельну (
<font color="#0000FF">.concurrent</font>
) чергу достатньо вказати при ініціалізації Private черзі значення аргументу
<font color="#0000FF">attributes</font>
рівне
<font color="#0000FF">.concurrent</font>
. Якщо ви не вказуєте цей аргумент, то
Private
чергу буде послідовною (
<font color="#0000FF">.serial</font>
). Аргумент
<font color="#0000FF">qos</font>
також не потрібно і може бути пропущений без всяких проблем.

Давайте призначимо нашої паралельної черзі
<font color="#0000FF">workerQueue</font>
якість обслуговування
<font color="#0000FF">qos</font>
, рівне
<font color="#0000FF">.userInitiated</font>
, і поставимо асинхронно в цю чергу спочатку завдання

, а потім

Наша нова паралельна чергу
<font color="#0000FF">workerQueue</font>
дійсно є паралельною, і завдання в ній виконуються одночасно, хоча все, що ми зробили в порівнянні з четвертим експериментом (одна послідовна чергу
<font color="#0000FF">serialPriorityQueue</font>
), це задали аргумент
<font color="#0000FF">attributes</font>
дорівнює
<font color="#0000FF">.concurrent</font>
:



Картина зовсім інша в порівнянні з однієї послідовної чергою. Якщо там усі завдання виконуються строго в тому порядку, в якому вони надходять на виконання, то для нашої паралельної (многопоточной) черги
<font color="#0000FF">workerQueue</font>
, яка може «розщеплюватися» на кілька потоків, завдання дійсно виконуються паралельно: деякі завдання з символом

, будучи пізніше поставлені в чергу
<font color="#0000FF">workerQueue</font>
, виконуються швидше на паралельному потоці.

Давайте використаємо паралельні
Private
черзі з різними пріоритетами:

чергу
<font color="#0000FF">workerQueue1</font>
c
<font color="#0000FF">qos .userInitiated</font>

чергу
<font color="#0000FF">workerQueue2</font>
c
<font color="#0000FF">qos .background</font>




Тут така ж картина, як і з різними послідовними
Private
чергами у другому експерименті. Ми бачимо, що завдання частіше виконуються на черзі
<font color="#0000FF">workerQueue1</font>
, що має більш високий пріоритет.

Можна створювати черги з відкладеним виконанням з допомогою аргументу
<font color="#0000FF">attributes</font>
, а потім активувати виконання завдань на неї в будь-який зручний час за допомогою методу
<font color="#0000FF">activate()</font>
:



6. Шостий експеримент пов'язаний з використанням DispatchWorkItem об'єктів
Якщо ви хочете мати додаткові можливості по управлінню виконанням різних завдань на
Dispatch
чергах, то можна створити
<font color="#0000FF">DispatchWorkItem</font>
, для якого можна задати якість обслуговування
<font color="#0000FF">qos</font>
, і воно буде впливати на його виконання:



Задаючи прапор
<font color="#0000FF">[.enforceQoS]</font>
при підготовці
<font color="#0000FF">DispatchWorkItem</font>
, ми отримуємо більш високий пріоритет для завдання
<font color="#0000FF">highPriorityItem</font>
перед іншими завданнями на тій же черзі:



Це дозволяє примусово підвищувати пріоритет виконання конкретного завдання на
<font color="#0000FF">Dispatch Queue</font>
c певним якістю обслуговування
<font color="#0000FF">qos</font>
і, таким чином, боротися з явищем «інверсія пріоритетів». Ми бачимо, що незважаючи на те, що два завдання
<font color="#0000FF">highPriorityItem</font>
стартують самими останніми, вони виконується на самому початку завдяки прапору
<font color="#0000FF">[.enforceQoS]</font>
та підвищення пріоритету до
<font color="#0000FF">.userInteractive</font>
. Крім того, завдання
<font color="#0000FF">highPriorityItem</font>
може запускатися багаторазово на різних чергах.

Якщо ми приберемо прапор
<font color="#0000FF">[.enforceQoS]</font>
:



завдання
<font color="#0000FF">highPriorityItem</font>
будуть брати то якість обслуговування
<font color="#0000FF">qos</font>
, який встановлено для черзі, на якій вони запускаються:



Але все одно вони потрапляють в самий початок відповідних черг. Код для всіх цих експериментів знаходиться на firstPlayground.playground на Github.

У класу
<font color="#0000FF">DispatchWorkItem</font>
є властивість
<font color="#0000FF">isCancelled</font>
та ряд методів:



Незважаючи на присутність методу
<font color="#0000FF">cancel()</font>
<font color="#0000FF">DispatchWorkItem</font>
GCD
все ще не дозволяє видаляти замикання, які вже стартували на визначеній черзі. Що ми можемо в даний час — це позначити
<font color="#0000FF">DispatchWorkItem</font>
«віддалену» з допомогою методу
<font color="#0000FF">cancel()</font>
. Якщо виклик методу
<font color="#0000FF">cancel()</font>
відбувається перед тим, як
<font color="#0000FF">DispatchWorkItem</font>
буде поставлена в чергу за допомогою методу
<font color="#0000FF">async</font>
,
<font color="#0000FF">DispatchWorkItem</font>
не буде виконуватися. Одна з причин, чому іноді необхідно використовувати механізм
Operation
, а не
GCD
, полягає як раз в тому, що
GCD
не вміє видаляти замикання, які стартували на визначеній черзі.

Можна використовувати клас
<font color="#0000FF">DispatchWorkItem</font>
та його метод
<font color="#0000FF">notify (queue:, execute:)</font>
, а також метод екземпляра класу
<font color="#0000FF">DispatchQueue</font>


async(execute workItem: DispatchWorkItem)

для вирішення завдання, наведеною в самому початку посту — завантаження зображення з мережі:



Ми формуємо синхронне завдання у вигляді примірника
<font color="#0000FF">workItem</font>
класу
<font color="#0000FF">DispatchWorlItem</font>
, складається в отримання даних
<font color="#0000FF">data</font>
з «мережі» по заданому
<font color="#0000FF">imageURL</font>
адресою. Виконуємо асинхронно завдання
<font color="#0000FF">workItem</font>
на паралельній глобальної черзі
<font color="#0000FF">queue</font>
з якістю обслуговування
<font color="#0000FF">qos: .utility</font>
з допомогою функції
queue.async(execute: workItem)


За допомогою функції
workItem.notify(queue: DispatchQueue.main) {
if let imageData = data {
eiffelImage.image = UIImage(data: imageData)}
}

ми чекаємо повідомлення про закінчення завантаження даних в
<font color="#0000FF">data</font>
. Як тільки це сталося, ми оновлюємо зображення елемента
UI
<font color="#0000FF">eiffelImage</font>
:



Код знаходиться на LoadImage.playground на Github.

Патерн 1. Варіанти коду для завантаження зображення з мережі
У нас є дві синхронні завдання:
отримання даних з мережі
let data = try? Data(contentsOf: imageURL)

та оновлення на основі даних
<font color="#0000FF">data</font>
користувальницького інтерфейсу (
UI
)
eiffelImage.image = UIImage(data: data)

Це типовий патерн, який виконується за допомогою механізмів багатопоточності
GCD
, коли потрібно виконати деяку роботи у фоновому потоці, а потім повернути результат у основний потік для відображення, так як компоненти
UIKit
може працювати виключно з головного потоку.

Це можна зробити або класичним способом:



або з допомогою готового асинхронного API, використовуючи
<font color="#0000FF">URLSession</font>
:



або за допомогою
<font color="#0000FF">DispatchWorlItem</font>
:



Нарешті, ми можемо завжди самі «загорнути» нашу синхронну задачку в асинхронну оболонку і виконати її:



Код для цього патерну знаходиться на LoadImage.playground на Github.

Патерн 2. Особливості завантаження зображень з мережі для Table View і Collection View з допомогою GCD
Розглянемо в якості прикладу дуже просте додаток, що складається всього з одного
Image Table View Controller
, у якого клітинки таблиці містять тільки зображення, завантажені з інтернету і індикатор активності, який показує процес завантаження:



Ось як виглядає клас
<font color="#0000FF">ImageTableViewController</font>
, обслуговуючий екранний фрагмент
Image Table View Controller
:



і клас
<font color="#0000FF">ImageTableViewCell</font>
для клітинки таблиці, в яку завантажується зображення:



Завантаження зображення проводиться звичайним класичним способом. Моделлю для класу
<font color="#0000FF">ImageTableViewController</font>
є масив з 8
URLs
:

  1. Ейфелева вежа
  2. Венеція
  3. Шотландський замок
  4. Супутник «Кассіні» — завантажується з мережі значно довше інших
  5. Ейфелева вежа
  6. Венеція
  7. Шотландський замок
  8. Арктика
Якщо ми запустимо програму і почнемо прокручувати досить швидко вниз з тим, щоб побачити всі 8 зображень, то ми виявимо, що Супутник «Кассіні» так і не завантажиться до тих пір, поки ми покинемо екран. Очевидно, що йому потрібно значно більше часу для завантаження, ніж усім іншим.



Зате прокрутивши до кінця і побачивши в самої останньої клітинки «Арктику», ми раптом виявляємо, що через деякий дуже невеликий час вона буде замінена на Супутник «Кассіні»:



Це неправильне функціонування такого простого додатка. У чому ж справа? Справа в тому, що комірки в таблиці є повторно-використовуються завдяки методом
<font color="#0000FF">dequeueReusableCell</font>
. Кожен раз, коли комірка (нова або повторноиспользуемая) потрапляє, а екран, запускається асинхронно завантаження зображення з мережі (в цей час крутиться «коліщатко»), як тільки завантаження виконана і зображення отримано, відбувається оновлення
UI
цієї комірки. Але ми не чекаємо завантаження зображення, ми продовжуємо прокручувати таблицю і комірка («Кассіні») йде з екрану, так і не оновивши свій
UI
. Однак знизу повинно з'явиться нове зображення і ця ж клітинка, пішла з екрану, буде використана повторно, але вже для іншого зображення (" Арктика"), яке швидко завантажиться і оновить
UI
. В цей час повернеться запущена в цій осередку раніше завантаження «Кассіні» і оновить екран, що неправильно.Це відбувається тому, що ми запускаємо різні речі, які працюють з мережею в різних потоках. Вони повертаються в різний час:



Як ми можемо виправити ситуацію? У межах механізму
GCD
ми не можемо скасувати завантаження зображення пішла з екрану клітинки, але ми можемо, коли приходять наші
<font color="#0000FF">imageData</font>
з мережі, перевірити
URL
, який викликав завантаження цих даних,
<font color="#0000FF">url</font>
і порівняти його з тим, який користувач хоче мати в цьому осередку в даний момент,
<font color="#0000FF">imageURL</font>
:



Тепер все буде працювати правильно. Таким чином, багатопоточне програмування вимагає нестандартного уяви. Справа в тому, що деякі речі в багатопотоковому програмуванні здійснюються в іншому порядку, ніж написаний код. Додаток GCDTableViewController знаходиться на Github.

Патерн 3. Використання груп DispatchGroup
Якщо у вас є кілька завдань, які потрібно виконати асинхронно, і дочекатися їх повного завершення, то застосовується група
<font color="#0000FF">DispatchGroup</font>
, яку дуже легко створити:

let imageGroup = DispatchGroup()


Припустимо, нам потрібно завантажити з мережі» 4 різних зображення:



Метод
<font color="#0000FF">queue.async(group: imageGroup)</font>
щоб додати групу будь-яке завдання (синхронне), що виконується на будь-якій черзі
<font color="#0000FF">queue</font>
:



Ми створюємо групу
<font color="#0000FF">imageGroup</font>
і поміщаємо в цю групу за допомогою методу
<font color="#0000FF">async (group: imageGroup)</font>
два завдання для асинхронної завантаження зображень в глобальну паралельну чергу
<font color="#0000FF">DispatchQueue.global()</font>
та два завдання асинхронної завантаження зображень в глобальну паралельну чергу
<font color="#0000FF">DispatchQueue.global(qos:.userInitiated)</font>
з якістю обслуговування
<font color="#0000FF">.userInitiated</font>
. Важливо, що в одну і ту ж групу можна додавати завдання, що функціонують на різних чергах. Коли всі завдання в групі будуть виконані, викликається функція
<font color="#0000FF">notify</font>
— це свого роду блок зворотного виклику на всю групу, який і розміщує всі зображення на екрані одночасно:



Група містить потоко-безпечний внутрішній лічильник, який автоматично збільшується при додаванні завдання в групу за допомогою методу
<font color="#0000FF">async (group: imageGroup)</font>
. Коли якесь завдання виконується, то лічильник зменшується на одиницю і нам гарантують, що блок зворотного виклику буде викликаний після завершення всіх довготривалих операцій. Експерименти з формуванням групи синхронних операцій представлені наPlayground GroupSyncTasks.playground на Github.

Якщо у вашій групі є не тільки синхронні операції, але і асинхронні, то потокобезопасным лічильником можна керувати вручну: метод
<font color="#0000FF">enter()</font>
збільшує лічильник, а метод
<font color="#0000FF">leave()</font>
зменшує. Розміщення асинхронних операцій в групі ми будемо вивчати за допомогою Playground GroupAsyncTasks.playground на Github. Ми будемо розміщувати асинхронні завдання в групу і відображати у верхній частині екрана.



Для порівняння в нижній частині екрана ми будемо розміщувати ті ж зображення, отримані звичайним чином, один за іншим, не упаковуючи їх у групи. Ви відразу відчуєте різницю в появі одних і тих же зображень у верхній частині екрана в нижній: спочатку з'являться один за іншим зображення в нижній частині екрана, а потім разом всі зображення верхній частині екрана, хоча виклик методів
<font color="#0000FF">asyncGroup ()</font>
та
<font color="#0000FF">asyncUsual ()</font>
відбувався в зворотному порядку:



Можливе розміщення в групі змішаних операцій: синхронних і асинхронних:



Результат буде той же.

Патерн 4. Поточно-безпечні (thread-safe) змінні. Черги ізоляції
Повернемося до нашим першим експериментів з чергами
GCD 
на
Swift 3
і спробуємо зберегти хронологічну (вертикальну) послідовність виконання завдань у рядку, і тим самим представити користувачеві результат виконання завдань на різних чергах в горизонтальному вигляді:



Скажу відразу, що я використовувала для накопичення результатів як звичайну НЕпоточно-безпечну
Swift 3
рядок
<font color="#0000FF">usualString: String</font>
, так і поточно-безпечну (thread-safe) рядок
<font color="#0000FF">safeString: ThreadSafeString</font>
:

var safeString = ThreadSafeString("")
var usualString = ""


Мета даного розділу полягає у тому, щоб показати, як повинна бути влаштована поточно-безпечна рядок
Swift 3
, тому про це трохи пізніше.

Всі експерименти з поточно-безпечною рядком будуть відбуватися на Playground GCDPlayground.playground в Github.

Я трохи зміню завдання з метою накопичення інформації в обох рядках
<font color="#0000FF">usualString</font>
та
<font color="#0000FF">safeString</font>
:



В
Swift
будь-яка змінна, декларована з ключовим словом
<font color="#0000FF">let</font>
є константою, а отже, і поточно-безпечною (
thread-safe
). Декларація змінної з ключовим словом
<font color="#0000FF">var</font>
робить змінну змінною (
mutable
) і непотокобезопасной (
thread-safe
) до тих пір, поки вона не буде сконструйована спеціальним чином. Якщо два потоки почнуть змінювати одночасно один і той же блок пам'яті, то може статися пошкодження цього блоку пам'яті. Крім того, якщо читати якусь змінну на одному потоці в той час, коли йде оновлення її значення на іншому потоці, то ви ризикуєте вважати «старе значення», тобто має місце стан гонки (
race condition
).

Ідеальним варіантом для поточно-безпеки був би випадок, коли

  • читання трапляються синхронно і багатопоточність
  • записи повинні бути асинхронними і повинні бути єдиною задачею, яка працює в даний момент з даної змінної
На щастя,
GCD
надає нам елегантний спосіб вирішення за допомогою бар'єрів (
barrier
) і черг ізоляції:



Бар'єри
GCD
роблять одну цікаву річ — вони чекають моменту, коли черга буде повністю порожня, перед тим як виконати своє замикання. Як тільки бар'єр починає виконувати своє замикання, він забезпечує, щоб чергу не виконувала ніякі інші замикання протягом цього часу і по суті працює як синхронний функція. Як тільки замикання з бар'єром закінчується, чергу повертається до своєї звичайної роботи, забезпечуючи гарантію того, що жодна запис не буде проводитися одночасно з читанням або іншим записом.

Давайте подивимося, як буде виглядати потокобезопасный клас
<font color="#0000FF">ThreadSafeString</font>
:



Функція
<font color="#0000FF">isolationQueue.sync</font>
відправить замикання «читання»
<font color="#0000FF">{result = self.internalString}</font>
в нашу чергу ізоляції
<font color="#0000FF">isolationQueue</font>
і буде чекати закінчення, перед тим як повернути результат виконання
<font color="#0000FF">result</font>
. Після цього у нас буде результат читання. Якщо не робити виклик синхронним, тоді буде потрібно введення блоку зворотного виклику. Завдяки тому, що черга
<font color="#0000FF">isolationQueue</font>
паралельна (
<font color="#0000FF">.concurrent</font>
), такі синхронні читання можуть виконуватися по кілька штук одночасно.

Функція
<font color="#0000FF">isolationQueue.async (flags: .barrier)</font>
відправить замикання «записи», «додавання» або «ініціалізації» в чергу ізоляції
<font color="#0000FF"> isolationQueue</font>
. Функція
<font color="#0000FF">async</font>
означає, що управління буде повернуто до того, як замикання «записи», «додавання» або «ініціалізації» фактично виконатися. Бар'єрна частина
<font color="#0000FF">(flags: .barrier)</font>
означає, що замикання не буде виконано до тих пір, поки кожне замикання в черзі не закінчить своє виконання. Інші замикання будуть розміщені після бар'єрного і виконуватися після того, як виконається бар'єрне.
Результати експериментів з
<font color="#0000FF">DispatchQueues</font>
, представлені поточно-безпечною (
thread-safe
) рядком
<font color="#0000FF">safeString: ThreadSafeString</font>
та звичайної рядком
<font color="#0000FF">usualString: String</font>
, знаходяться на Playground GCDPlayground.playground на Github.

Давайте подивимося на ці результати.

1. Функція
<font color="#0000FF">sync</font>
на Глобальній паралельної черзі
<font color="#0000FF">DispatchQueue.global(qos: .userInitiated)</font>
по відношенню до
Playground
:



Результати на звичайній НЕпоточно-безпечної рядку
<font color="#0000FF">usualString</font>
, СПІВПАДАЮТЬ з результатами поточно-безпечною рядку
<font color="#0000FF">safeString</font>
.

2. Функція
<font color="#0000FF">async</font>
на Глобальній паралельної черзі
<font color="#0000FF">DispatchQueue.global(qos: .userInitiated)</font>
по відношенню до
Playground
:



Результати на звичайній НЕпоточно-безпечної рядку
<font color="#0000FF">usualString</font>
, НЕ ЗБІГАЮТЬСЯ з результатами поточно-безпечною рядку
<font color="#0000FF">safeString</font>
.

3. Функція
<font color="#0000FF">sync</font>
на
Private
послідовній черзі
<font color="#0000FF">DispatchQueue (label: "com.bestkora.mySerial")</font>
по відношенню до Playground:



Результати на звичайній НЕ поточно-безпечною рядку
<font color="#0000FF">usualString</font>
, СПІВПАДАЮТЬ з результатами поточно-безпечною рядку
<font color="#0000FF">safeString</font>
.

4. Функція
<font color="#0000FF">async</font>
на
Private
послідовній черзі
<font color="#0000FF">DispatchQueue (label: "com.bestkora.mySerial")</font>
по відношенню до
Playground
:



Результати на звичайній НЕпоточно-безпечної рядку
<font color="#0000FF">usualString</font>
, НЕ ЗБІГАЮТЬСЯ з результатами поточно-безпечною рядку
<font color="#0000FF">safeString</font>
.

5. Функція
<font color="#0000FF">async</font>
для завдань

і

на
Private
послідовній черзі
<font color="#0000FF">DispatchQueue (label: "com.bestkora.mySerial", qos : .userInitiated)</font>
:



Результати на звичайній НЕ поточно-безпечною рядку
<font color="#0000FF">usualString</font>
, СПІВПАДАЮТЬ з результатами поточно-безпечною рядку
<font color="#0000FF">safeString</font>
.

6. Функція
<font color="#0000FF">async</font>
для завдань

і

на різних
Private
послідовних чергах
<font color="#0000FF">DispatchQueue (label: "com.bestkora.mySerial", qos : .userInitiated)</font>
та
<font color="#0000FF">DispatchQueue (label: "com.bestkora.mySerial", qos : .background)</font>
:



Результати на звичайній НЕпоточно-безпечної рядку
<font color="#0000FF">usualString</font>
, НЕ ЗБІГАЮТЬСЯ з результатами поточно-безпечною рядку
<font color="#0000FF">safeString</font>
.

7. Функція
<font color="#0000FF">async</font>
для завдань

і

на
Private
паралельної черзі
<font color="#0000FF">DispatchQueue (label: "com.bestkora.mySerial", qos : .userInitiated, attributes: .concurrent)</font>
:



Результати на звичайній НЕпоточно-безпечної рядку
<font color="#0000FF">usualString</font>
, НЕ ЗБІГАЮТЬСЯ з результатами поточно-безпечною рядку
<font color="#0000FF">safeString</font>
.

8. Функція
<font color="#0000FF">async</font>
для завдань

і

на різних
Private
паралельних чергах з
<font color="#0000FF">qos : .userInitiated</font>
та
<font color="#0000FF">qos : .background</font>
:



Результати на звичайній НЕпоточно-безпечної рядку
<font color="#0000FF">usualString</font>
, НЕ ЗБІГАЮТЬСЯ з результатами поточно-безпечною рядку
<font color="#0000FF">safeString</font>
.

9. Функція
<font color="#0000FF">asyncAfter (deadline: .now() + 0.0, qos: .userInteractive)</font>
c зміною пріоритету:



Результати на звичайній НЕпоточно-безпечної рядку
<font color="#0000FF">usualString</font>
, НЕ ЗБІГАЮТЬСЯ з результатами поточно-безпечною рядку
<font color="#0000FF">safeString</font>
.

10. Функція
<font color="#0000FF">asyncAfter (deadline: .now() + 0.1, qos: .userInteractive)</font>
c зміною пріоритету:



Результати на звичайній НЕ поточно-безпечною рядку
<font color="#0000FF">usualString</font>
, СПІВПАДАЮТЬ з результатами поточно-безпечною рядку
<font color="#0000FF">safeString</font>
, так як завдання

і

рознесені в часі.
Скрізь, де є багатопоточне виконання завдань

і

відбувається або на різних чергах, або на одній, але паралельної (
<font color="#0000FF">.concurrent</font>
) черги, ми спостерігаємо розбіжність звичайної рядка
<font color="#0000FF">usualString</font>
з поточно-безпечною рядком
<font color="#0000FF">safeString</font>
.

Використовуючи поточно-безпечну рядок
<font color="#0000FF">safeString</font>
ми можемо поглянути на властивості черг і функцій sync і async, так би мовити, «з висоти пташиного польоту», праворуч приводиться час виконання відповідних завдань:



Якщо ви використовуєте не
Playground
, а додаток, то в
Xcode 8
є можливість використовувати
Thread Sanitizer
для визначення
race condition
.
Thread Sanitizer
працює на етапі виконання програми. Запустити його можна шляхом редагування Схеми (
Scheme
):



Ви бачите виявлення race condition для нашого прикладу. Код програми Tsan знаходиться на Github.

ВИСНОВОК.

Ми розглянули деякі приклади використання
GCD
для вирішення питань багатопоточного програмування в
Swift 3
. Наступна стаття буде присвячена питанням використання
Operations 
в практиці багатопоточного програмування на
Swift 3
.

p.s.
GCD API
є на всіх платформах, і забезпечує прекрасний спосіб створення багатопоточних додатків. Але поточна версія
Swift 3
не має ніяких синтаксичних конструкцій для опису багатопоточності. Команда розробників
Swift
планує взятися за багатопоточність більш інтенсивно і підготувати реальні зміни в синтаксисі багатопоточності у версії
Swift 5
(2018 р.), розпочавши обговорення Навесні/Влітку 2017 р., випустивши "manifesto" до Осені 2017р…

Кріс Латнер наДні Мов програмування IBM розповідав, що існуюче багатопоточне програмування з використання
GCD API
та
<font color="#0000FF">async</font>
функції призводить до «піраміди смерті» (pyramid of doom), в якій дуже важко розпізнати без коментарів, які дані/стану «володіють» якими
Dispatch Queue
і відповідними завданнями, виконуваними на цих чергах:



Одне із можливий напрямків поліпшення багатопоточності — це використання Моделі акторів (actor models). Кожен
<font color="#0000FF">actor</font>
— це, фактично,
<font color="#0000FF">DispatchQueue</font>
+ Стан, яким ця чергу управляє, +Операції, що виконуються на цій черзі:



Але це всього лише одне з багатьох пропозицій. Предполагается розглянути
<font color="#0000FF">actors</font>
,
<font color="#0000FF">async/await</font>
, atomicity, memory models та інші пов'язані з цим теми. Багатопоточність дуже важлива, так як вона «відкриває двері» новим підходам як на клієнті, так і на сервері.

За еволюцією
Swift
можна дивитися тепер здесь.

Ця стаття може бути корисна тим, хто збирається вивчати iOS програмування на Swift c допомогоюстэнфордских курсів CS193p Winter 2016-17 (заснованих на iOS 10 і Swift 3), які будуть розміщені на iTunes в кінці січня 2017 — початку лютого 2017, бо там передбачається значний обсяг багатопоточного програмування.

Посилання
WWDC 2016. Concurrent Programming With GCD in Swift 3 (session 720)
WWDC 2016. Improving Existing Apps with Modern Best Practices (session 213)
WWDC 2015. Building Responsive and Efficient Apps with GCD.
Grand Central Dispatch (GCD) and Dispatch Queues in Swift 3
iOS Concurrency with GCD and Operations
The GCD Handbook
Куховарська книга GCD
Modernize libdispatch for Swift 3 naming conventions
GCD
GCD – Beta
CONCURRENCY IN IOS
Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.