libdispatch
(aka GCD
) – библиотека предоставляющая доступ к высокоуровневому API для конкурентного выполнения задач с управлением потоками за сценой. GCD
абстрагируется от кода управления потоками и переносит его на системный уровень, предоставляя легкий API для определения задач и их выполнения в соответствующей очереди диспетчеризации. GCD
работает на системном уровне, таким образом он может удовлетворить потребности всех запущенных приложений на девайсе, при этом управляя ресурсами эффективно.
Queue
(очередь) представляет собой сущность, выполняющую задачи, поступающие на вход, на одном или множестве потоков. Очередь работает по принципу FIFO (first in, first out), таким образом первая задача на очереди будет первой направлена на выполнение на потоке.
В iOS главный поток приложения - это очередь, состоящая из процессов. Главный поток должен использоваться для всех View, а элементы интерфейса не должны блокироваться выполняющимися задачами. Разработчику следует избегать функций, использующих главный поток для загрузки данных, изображений и т.д.
flowchart TD
A(2 queue)
A --> Serial("serial");
A --> Concurrent("concurrent");
serial
– выполняет задачи последовательно (поочередно). До тех пор, пока задача не будет выполнена, поток не приступит к выполнения следующей задачи в очереди.concurrent
– выполняет задачи конкурентно. Задачи, поступающие в concurrent очередь, могут выполняться одновременно на разных потоках.
flowchart TD
A("3 типа queues")
A --> Main("Main");
A --> Global("Global");
A --> Custom("Custom");
GCD
предоставляет 3 типа queues –
Main queue
: Serial queue – работает на main thread.Global queue
: Concurrent queue – работает с разным приоритетом и доступен всей системе.Custom queue
: Serial/ Concurrent queue.
Quality of Service
(QOS) - это приоритет выполнения задачи в GCD
. Если task
имеет более высокий qos
, чем другие, оно будет обработано раньше, чем task
с более низким приоритетом.
QOS
ранжируется от высшего к низшему:
User Interactive
: Задача, которая запускается в основном потоке, например, анимация или операции рисования.User Initiated
: Задача, которую запускает пользователь и которая должна дать немедленные результаты. Эта задача должна быть завершена, чтобы пользователь мог продолжить работу.Utility
: Задача, которая может занять некоторое время и не требует немедленного завершения. Аналогично индикаторам выполнения и импорту данных.Background
: Данная задача не видна пользователю. Резервное копирование, синхронизация, индексирование и т.д.
Data Race
- это ситуация, когда две инструкции из двух разных потоков пытаются получить доступ к одному и тому же участку памяти, при это один из этих доступов на запись и между ними нет никакой синхронизации.
Это семантическая ошибка, возникающая во времени или порядке событий, которая приводит к неверному поведению программы. Race Condition может быть вызвана Data Race
, но может быть вызвана и другими причинами. Одним из решений может быть NSLock()
Deadlock
(тупик) возникает, когда две, а иногда и больше задач ожидают завершения другой задачи, но ни одна из них так и не завершается. Первое действие не может быть закончено, потому что оно ожидает окончания второго. А второе не может закончено, потому что оно ожидает окончания первого.
Priority inversion
(инверсия приоритетов) - это критическое состояние в потоках, когда поток с низким приоритетом блокирует выполнение потока с высоким приоритетом и делает назначенные приоритеты бессмысленными для потока. Такая ситуация возникает, когда поток с более низким приоритетом захватил какой-то общий ресурс и использует его, тем самым поток с высоким приоритетом не может использовать этот ресурс.
NSOperation
построен поверх libdispatch
для упрощения работы с несколькими потоками. Используя NSOperation
можно добавить зависимость для различных операций, повторно использовать, отменить или приостановить их.
ThreadPool
— пул потоков, которые повторно используют фиксированное число потоков для выполнения конкретной задачи.
Semaphore
предоставляет нам возможность контролировать доступ нескольких поток к общему ресурсу. Семафор состоит из очереди потоков и счетчика значений. Счетчик значений используется семафором, чтобы решить, должен ли поток получить доступ к общему ресурсу или нет. Счетчик значений изменяется при вызове методов signal()
или wait()
:
class DispatchSemaphore : DispatchObject {
func signal() -> Int { } // Signals (increments) a semaphore.
func wait() { } // Waits for, or decrements, a semaphore.
}
Task
- юнит асинхронной работы, в котором выполняется async
функция:
@frozen struct Task<Success, Failure> where Success : Sendable, Failure : Error
Task
позволяют создавать конкурентное окружение из не-конкурентных методов с помощью вызова async/await
.
Задача выполняется немедленно после объявления и не требует явного запуска.
Задачи имеют приоритет, их можно отменять, и они могут находиться в трёх состояниях - приостановленные, выполняющиеся и завершенные. Могут быть структурированными и неструктурированными.
За приоритеты в Task
отвечает структура TaskPriority
, которая содержит следующие приоритеты:
flowchart TD
A("Task Priority")
A --> high("high");
A --> medium("medium");
A --> low("low");
A --> userInitiated("userInitiated");
A --> background("background");
A --> utility("utility");
high
medium
low
userInitiated
background
utility
Дочерние таски автоматически наследуют приоритет родительской.
Detached
таски, созданные методом detach(priority:operation:)
, не наследуют приоритет, поскольку они не присоединены к текущей задаче.
TaskGroup
— группа, содержащая динамически созданные дочерние таски:
@frozen struct TaskGroup<ChildTaskResult> where ChildTaskResult : Sendable
Для создания группы, вызовите метод withTaskGroup(of:returning:body:)
.
Не используйте группу вне области Task. В большинстве случаев, система типов Swift
не позволит выйти из области видимости task group, поскольку добавление дочерней задачи в task group является операцией мутации, которые не могут быть выполнены из конкурентного контекста.
actor — reference type, который защищает доступ к изменяемому состоянию и объявляется с помощью ключевого слова actor
. Тип данных, который можно безопасно использовать в конкурентной среде. Содержит следующие характеристики:
- Имеют собственное изолированное состояние.
- Может содержать логику для изменения собственного состояния.
- Может общаться с другими участниками только асинхронно (через их адреса).
- Может создавать других дочерних акторов (в данном случае нас это не особо волнует).
await
— это специальная пометка, которая говорит о том, что в этом месте может быть suspension point
.
Suspension points
(точки приостановки) – динамическая концепция всей программы, которая гласит: существуют места, которые могут фактически приостанавливать выполнение задачи и оставить поток.
Potential suspension points
- are the static, conservative, function-local view of suspension points: they're the points in the function where a suspension could potentially occur and therefore the function ought to be prepared to abandon the thread. Any dynamic suspension point is always "inside" a potential suspension point from the perspective of every async function on that task: all of the outer functions must be awaiting an asynchronous call, and the innermost function must be awaiting at the actual dynamic suspension point. From all of those functions' local perspectives, the exact reason for the suspension (which may be N levels of call deep) is much less important than the fact that a suspension is possible at that point in their execution.