❗ Прежде чем приступать к изучению многопоточности нужно прочитать про Concurrency(параллельность) and Multitasking(многозадачность)
Говоря о параллелизме (concurrency) в Swift, обычно имеют в виду очереди, которые содержат в себе thread (потоки).
Очередь — это массив задач, каждая из которых начинает выполнение в порядке их добавления.
Существует два типа очередей: последовательные(serial) и параллельные(concurrent).
- В последовательной (serial) очереди задачи выполняются одна за другой. Следующая задача не будет запущена, пока предыдущая не будет завершена.
Одновременно может выполняться только одна задача
- В параллельной (concurrent) очереди задачи также выполняются одна за другой. Однако каждая задача выполняется в отдельном потоке, и очередь не ждет, пока предыдущая задача запустит следующую. Следующая задача запускается немедленно, если ресурсов достаточно для создания еще одного потока.
Ниже описаны термины в привязке к GCD, но с небольшими изменениями они верны и для программирования в целом.
Синхронная операция начинает выполнятся сразу при вызове, блокируя текущий поток. Выполняется в текущем потоке.
Асинхронная операция ставит задачу в очередь выполнения, продолжает выполнение кода, из которого вызвана задача. Если очередь однопоточная, то задача будет выполнятся после выполнения всех задач, которые уже поставлены в очередь, если многопоточная - возможно ее выполнение в другом потоке.
Нужно запомнить, что синхронность-асинхронность и однопоточность-многопоточность две пары разных характеристить, и одни не зависят от других (и синхронность и асинхронность могут работать как в однопоточной среде, так и в многопоточной)
Существует три способа достижения параллелизма в iOS:
- Потоки (threads);
- GCD;
- NSOperationQueue;
- async await;
Последующие методы работы многопоточности в iOS - это всего лишь удобные обертки над unix потоками: судя по стеку вызовов NSThread использует pthread. Насчет dispatch он использует darwin-libpthread
Недостатком потоков является то, что они немасштабируемы для разработчика. Вы должны решить, сколько потоков нужно создать и изменять их число динамически в соответствии с условиями. Кроме того, приложение принимает на себя большую часть затрат, связанных с созданием и встраиванием потоков, которые оно использует.
Поэтому в macOS и iOS предпочтительно использовать асинхронный подход к решению проблемы параллелизма, а не полагаться на потоки.
Одной из технологий асинхронного запуска задач является Grand Central Dispatch (GCD), которая отводит управление потоками до уровня системы. Все, что разработчик должен сделать, это определить выполняемые задачи и добавить их в соответствующую очередь отправки. GCD заботится о создании необходимых потоков и время для работы в этих потоках.
Все dispatch queues
представляют собой структуры данных FIFO, поэтому задачи всегда запускаются в том же порядке, в котором они добавлены.
В отличие от dispatch queue очереди операций (NSOperation Queue) не ограничиваются выполнением задач в порядке FIFO и поддерживают создание сложных графиков выполнения заказов для ваших задач.
Мьютекс — это семафор, работающий с системой блокировки
При написании многопоточных приложений требуется работать с общими данными из разных потоков и синхронизировать их. Для синхронизации потоков существуют объекты синхронизации - мьютекс (в iOS SDK они реализуются в виде NSLock и NSRecursiveLock).
Мьютекс является одним из видов семафора, который предоставляет доступ одновременно только одному потоку. Если мьютекс используется и другой поток пытается получить его, что поток блокируется до тех пор, пока мьютекс не освободится от своего первоначального владельца. Если несколько потоков соперничают за одни и те же мьютексы, только одному будет разрешен к нему доступ.
Семафор позволяет выполнять какой-либо участок кода одновременно только конкретному количеству потоков. В основе семафора лежит счетчик, который и определяет, можно ли выполнять участок кода текущему потоку или нет. Если счетчик больше нуля — поток выполняет код, в противном случае — нет. В GCD выглядит так: semaphore_create – создание семафора (аналог sem_init) semaphore_destroy – удаление, соответственно (аналог sem_destroy) semaphore_wait – блокирующее ожидание на семафоре (аналог sem_wait) semaphore_signal – освобождение семафора (аналог sem_post)
Раскрыть
Пример, где можно использовать мьютекс NSLock:
var counter = 0
let thread1 = Thread {
for _ in 0..<1000 {
counter += 1
}
}
let thread2 = Thread {
for _ in 0..<1000 {
counter += 1
}
}
thread1.start()
thread2.start()
// counter < 2000
Операция увеличения счетчика не атомарно. Оно состоит из нескольких ша:
let tmp = counter + 1
counter = tmp
Может произойти ситуация, когда оба потока могут оказаться в точке кода, производящей запись или чтение. Для исправления ситуации нужно синхронизовать обращение к счетчику.
var counter = 0
let lock = NSLock()
let thread1 = Thread {
for _ in 0..<1000 {
lock.lock()
counter += 1
lock.unlock()
}
}
let thread2 = Thread {
for _ in 0..<1000 {
lock.lock()
counter += 1
lock.unlock()
}
}
thread1.start()
thread2.start()
// counter < 2000
Участок кода между lock()
и unlock()
называется критическая секция. NSLock позволяет вызывать unlock()
только тому потоку с которого был вызван lock()
.
let lock = NSLock()
lock.lock()
lock.lock() // deadlock()
Исправление deadlock:
let lock = NSRecursiveLock()
lock.lock()
lock.lock()
lock.unlock()
lock.unlock()
3.2 Multithreading Theme Folder | Back To iOSWiki Contents | 3.2.2 Problems Of Multithreading Theme