Я изо всех сил пытаюсь полностью понять параллельные и последовательные очереди в GCD. У меня есть некоторые проблемы, и я надеюсь, что кто-нибудь ответит мне четко и по существу.
Я читаю, что последовательные очереди создаются и используются для выполнения задач одну за другой. Однако что произойдет, если:
- Я создаю последовательную очередь
- Я использую
dispatch_async
(в только что созданной последовательной очереди) три раза для отправки трех блоков A, B, C
Будут ли выполнены три блока:
в порядке A, B, C, потому что очередь последовательная
ИЛИ
- одновременно (в одно и то же время в параллельных потоках), потому что я использовал отправку ASYNC
Я читаю, что могу использовать
dispatch_sync
в параллельных очередях для выполнения блоков один за другим. В таком случае, ПОЧЕМУ вообще существуют последовательные очереди, поскольку я всегда могу использовать параллельную очередь, в которой я могу СИНХРОННО отправлять столько блоков, сколько захочу?Спасибо за хорошее объяснение!
ios
multithreading
concurrency
grand-central-dispatch
Богдан Александру
источник
источник
Ответы:
Простой пример: у вас есть блок, выполнение которого занимает минуту. Вы добавляете его в очередь из основного потока. Давайте посмотрим на четыре случая.
Очевидно, вы не стали бы использовать ни один из двух последних для длительных процессов. Обычно вы видите это, когда пытаетесь обновить пользовательский интерфейс (всегда в основном потоке) из того, что может выполняться в другом потоке.
источник
Вот несколько экспериментов , которые я сделал , чтобы заставить меня понять о них
serial
,concurrent
очередями сGrand Central Dispatch
.Вот краткое изложение этих экспериментов
Помните, что с помощью GCD вы только добавляете задачу в очередь и выполняете задачу из этой очереди. Очередь отправляет вашу задачу либо в основном, либо в фоновом потоке, в зависимости от того, является ли операция синхронной или асинхронной. Типы очередей: Последовательная, Параллельная, Основная очередь отправки. Вся задача, которую вы выполняете, по умолчанию выполняется из Главной очереди отправки. Для вашего приложения уже существует четыре предопределенных глобальных параллельных очереди и одна основная очередь (DispatchQueue.main). вы также можете вручную создать свою очередь и выполнять задачи из этой очереди.
Задача, связанная с пользовательским интерфейсом, всегда должна выполняться из основного потока путем отправки задачи в основную очередь. Утилита для коротких рук - это
DispatchQueue.main.sync/async
тогда, когда связанные с сетью / тяжелые операции всегда должны выполняться асинхронно, независимо от того, какой поток вы используете либо в основном, либо в фоновомРЕДАКТИРОВАТЬ: Однако есть случаи, когда вам нужно выполнять операции сетевых вызовов синхронно в фоновом потоке без замораживания пользовательского интерфейса (например, обновить токен OAuth и подождать, удастся ли он удастся или нет). Вам необходимо обернуть этот метод внутри асинхронной операции. операции выполняются в порядке и без блокировки основного потока.
РЕДАКТИРОВАТЬ РЕДАКТИРОВАТЬ: вы можете посмотреть демонстрационное видео здесь
источник
}
нему, потому что он действительно не выполняется в этот моментconcurrentQueue.sync
изdoLongSyncTaskInConcurrentQueue()
функции, он выводит основную нить,Task will run in different thread
кажется , не так.Во-первых, важно знать разницу между потоками и очередями и то, что на самом деле делает GCD. Когда мы используем очереди отправки (через GCD), мы действительно ставим в очередь, а не распределяем потоки. Инфраструктура Dispatch была разработана специально для того, чтобы увести нас от многопоточности, поскольку Apple признает, что «реализация правильного решения для многопоточности [может] стать чрезвычайно трудным, а иногда и невозможным». Следовательно, для одновременного выполнения задач (задач, которые мы не хотим, чтобы пользовательский интерфейс замораживался), все, что нам нужно сделать, это создать очередь этих задач и передать ее GCD. И GCD обрабатывает все связанные потоки. Поэтому все, что мы на самом деле делаем, - это выстраиваемся в очередь.
Второе, что нужно сразу знать, - что это за задача. Задача - это весь код в этом блоке очереди (не в очереди, потому что мы можем добавлять вещи в очередь все время, но в пределах закрытия, где мы добавили его в очередь). Задачу иногда называют блоком, а блок - задачей (но они чаще называются задачами, особенно в сообществе Swift). И независимо от того, сколько или мало кода, весь код в фигурных скобках считается одной задачей:
И очевидно, что упоминание о том, что «одновременный» означает «одновременно с другими», а «последовательный» означает одно за другим (никогда в одно и то же время). Сериализовать что-либо или поместить что-то в последовательный формат, означает просто выполнить это от начала до конца в порядке слева направо, сверху вниз, без прерывания.
Есть два типа очередей: последовательные и параллельные, но все очереди параллельны друг относительно друга . Тот факт, что вы хотите запускать любой код «в фоновом режиме», означает, что вы хотите запускать его одновременно с другим потоком (обычно с основным потоком). Следовательно, все очереди отправки, последовательные или параллельные, выполняют свои задачи одновременно по отношению к другим очередям . Любая сериализация, выполняемая очередями (последовательными очередями), имеет отношение только к задачам в этой единственной [последовательной] очереди отправки (как в приведенном выше примере, где две задачи находятся в одной последовательной очереди; эти задачи будут выполняться одна за другой). другой, никогда одновременно).
ПОСЛЕДОВАТЕЛЬНЫЕ Очереди (часто известные как частные очереди отправки) гарантируют выполнение задач по одной от начала до конца в том порядке, в котором они были добавлены в эту конкретную очередь. Это единственная гарантия сериализации при обсуждении очередей отправки.- что определенные задачи в определенной последовательной очереди выполняются последовательно. Однако последовательные очереди могут выполняться одновременно с другими последовательными очередями, если они являются отдельными очередями, потому что, опять же, все очереди параллельны друг относительно друга. Все задачи выполняются в разных потоках, но не каждая задача гарантированно запускается в одном потоке (не важно, но интересно знать). И в iOS-фреймворке нет готовых последовательных очередей, вы должны их создать. Частные (неглобальные) очереди по умолчанию являются последовательными, поэтому для создания последовательной очереди:
Вы можете сделать его параллельным через его свойство attribute:
Но на этом этапе, если вы не добавляете какие-либо другие атрибуты в частную очередь, Apple рекомендует вам просто использовать одну из их готовых к работе глобальных очередей (которые работают одновременно). Внизу этого ответа вы увидите другой способ создания последовательных очередей (с использованием свойства target), который Apple рекомендует делать (для более эффективного управления ресурсами). Но пока достаточно маркировки.
CONCURRENT QUEUES (часто называемые глобальными очередями отправки) могут выполнять задачи одновременно; Тем не менее, задачи гарантированно запускаются в том порядке, в котором они были добавлены в эту конкретную очередь, но, в отличие от последовательных очередей, очередь не ждет завершения первой задачи перед запуском второй задачи. Задачи (как и в случае с последовательными очередями) выполняются в разных потоках, и (как и в случае с последовательными очередями) не каждая задача гарантированно запускается в одном потоке (не важно, но интересно знать). Платформа iOS поставляется с четырьмя готовыми к использованию параллельными очередями. Вы можете создать параллельную очередь, используя приведенный выше пример или одну из глобальных очередей Apple (что обычно рекомендуется):
Есть два способа диспетчеризации очередей: синхронно и асинхронно.
SYNC DISPATCHING означает, что поток, в котором была отправлена очередь (вызывающий поток), приостанавливается после отправки очереди и ожидает завершения выполнения задачи в этом блоке очереди перед возобновлением. Для синхронной отправки:
ASYNC DISPATCHING означает, что вызывающий поток продолжает работать после отправки очереди и не ждет, пока задача в этом блоке очереди завершит выполнение. Для асинхронной отправки:
Теперь можно подумать, что для последовательного выполнения задачи следует использовать последовательную очередь, а это не совсем так. Для последовательного выполнения нескольких задач следует использовать последовательную очередь, но все задачи (изолированные сами по себе) выполняются последовательно. Рассмотрим этот пример:
Независимо от того, как вы настраиваете (последовательный или параллельный) или отправляете (синхронизирующий или асинхронный) эту очередь, эта задача всегда будет выполняться последовательно. Третий цикл никогда не будет выполняться перед вторым циклом, а второй цикл никогда не будет выполняться перед первым циклом. Это верно для любой очереди с любой диспетчеризацией. Это когда вы вводите несколько задач и / или очередей, где действительно вступают в игру последовательный интерфейс и параллелизм.
Рассмотрим эти две очереди, одну последовательную и одну параллельную:
Скажем, мы отправляем две параллельные очереди в асинхронном режиме:
Их вывод беспорядочный (как и ожидалось), но обратите внимание, что каждая очередь последовательно выполняла свою задачу. Это самый простой пример параллелизма - две задачи, выполняемые одновременно в фоновом режиме в одной очереди. А теперь сделаем первый серийник:
Разве первая очередь не должна выполняться последовательно? Было (и было второе). Что бы еще ни произошло в фоновом режиме, очередь не касается. Мы сказали последовательной очереди выполняться последовательно, и она сделала ... но мы дали ей только одну задачу. Теперь давайте дадим ему две задачи:
И это самый простой (и единственно возможный) пример сериализации - две задачи, выполняющиеся последовательно (одна за другой) в фоновом режиме (для основного потока) в одной очереди. Но если мы сделаем их двумя отдельными последовательными очередями (потому что в приведенном выше примере это одна и та же очередь), их вывод снова будет беспорядочным:
Именно это я имел в виду, когда сказал, что все очереди параллельны друг относительно друга. Это две последовательные очереди, выполняющие свои задачи одновременно (потому что это отдельные очереди). Очередь не знает других очередей и не заботится о них. Теперь давайте вернемся к двум последовательным очередям (из той же очереди) и добавим третью очередь, параллельную:
Это своего рода неожиданность, почему параллельная очередь ожидала завершения последовательных очередей, прежде чем она была выполнена? Это не параллелизм. Ваша игровая площадка может показывать другой результат, но моя показала это. И он показал это, потому что приоритет моей параллельной очереди был недостаточно высок, чтобы GCD мог выполнить свою задачу раньше. Поэтому, если я сохраню все то же самое, но изменю QoS глобальной очереди (ее качество обслуживания, которое является просто уровнем приоритета очереди)
let concurrentQueue = DispatchQueue.global(qos: .userInteractive)
, то результат будет таким, как ожидалось:Две последовательные очереди выполняли свои задачи последовательно (как и ожидалось), а параллельная очередь выполняла свою задачу быстрее, потому что ей был присвоен высокий уровень приоритета (высокий QoS или качество обслуживания).
Две параллельные очереди, как и в нашем первом примере печати, показывают беспорядочную распечатку (как и ожидалось). Чтобы заставить их печатать аккуратно в последовательном порядке, нам нужно было бы создать для них одну и ту же последовательную очередь (также один и тот же экземпляр этой очереди, а не одну и ту же этикетку) . Затем каждая задача выполняется последовательно по отношению к другой. Однако другой способ заставить их печатать по порядку - сохранить их одновременно, но изменить их метод отправки:
Помните, что диспетчеризация синхронизации означает только то, что вызывающий поток ожидает завершения задачи в очереди, прежде чем продолжить. Предостережение здесь, очевидно, заключается в том, что вызывающий поток замораживается до завершения первой задачи, что может или не может быть так, как вы хотите, чтобы пользовательский интерфейс выполнялся.
И именно по этой причине мы не можем делать следующее:
Это единственная возможная комбинация очередей и методов диспетчеризации, которую мы не можем выполнить - синхронная диспетчеризация в основной очереди. И это потому, что мы просим основную очередь «заморозиться», пока мы не выполним задачу в фигурных скобках ... которую мы отправили в основную очередь, которую мы просто заморозили. Это называется тупиком. Чтобы увидеть это в действии на детской площадке:
И последнее, о чем стоит упомянуть, - это ресурсы. Когда мы даем очереди задачу, GCD находит доступную очередь из своего пула, управляемого изнутри. Что касается написания этого ответа, для каждого qos доступно 64 очереди. Может показаться, что это много, но они могут быть быстро использованы, особенно сторонними библиотеками, особенно фреймворками баз данных. По этой причине у Apple есть рекомендации по управлению очередью (упомянутые в ссылках ниже); одно существо:
Для этого вместо того, чтобы создавать их, как мы делали раньше (что вы все еще можете), Apple рекомендует создавать такие последовательные очереди:
Для дальнейшего чтения рекомендую следующее:
https://developer.apple.com/library/archive/documentation/General/Conceptual/ConcurrencyProgrammingGuide/Introduction/Introduction.html#//apple_ref/doc/uid/TP40008091-CH1-SW1
https://developer.apple.com/documentation/dispatch/dispatchqueue
источник
Если я правильно понимаю, как работает GCD, я думаю, что есть два типа
DispatchQueue
,serial
иconcurrent
в то же время есть два способаDispatchQueue
диспетчеризации своих задач: назначенныйclosure
, первыйasync
и другойsync
. Вместе они определяют, как на самом деле выполняется закрытие (задача).Я обнаружил это
serial
иconcurrent
имел ввиду, сколько потоков может использовать очередь,serial
означает один, тогда какconcurrent
означает много. И ,sync
иasync
означает , что задание будет выполняться на какой поток, поток вызывающего или нить , лежащая в основе этой очереди,sync
значит работать на потоке вызывающего абонента в то время какasync
средство запуска на основной нити.Ниже приведен экспериментальный код, который можно запускать на игровой площадке Xcode.
Надеюсь, это может быть полезно.
источник
Мне нравится думать об этом, используя эту метафору (вот ссылка на исходное изображение):
Представьте, что ваш папа моет посуду, а вы только что выпили стакан содовой. Вы приносите стакан отцу, чтобы он вымыл, ставя его рядом с другой посудой.
Теперь ваш папа сам моет посуду, поэтому ему придется мыть посуду одну за другой: ваш папа представляет собой последовательную очередь .
Но вам не очень интересно стоять там и смотреть, как все убирают. Итак, вы бросаете стакан и возвращаетесь в свою комнату: это называется асинхронной отправкой . Ваш папа может сообщить вам, а может и не дать вам знать, когда он закончит, но важно то, что вы не ждете, пока стекло будет очищено; ты возвращаешься в свою комнату, чтобы заняться детскими вещами.
А теперь давайте предположим, что вы все еще хотите пить и хотите выпить воды на том же стакане, который вам нравится больше всего, и вы действительно хотите вернуть его, как только он будет вымыт. Итак, вы стоите и смотрите, как ваш папа моет посуду, пока не вымоет ваша. Это отправка синхронизации , поскольку вы заблокированы, пока ожидаете завершения задачи.
И, наконец, допустим, ваша мама решает помочь вашему папе и присоединяется к нему, мыть посуду. Теперь очередь становится параллельной, так как они могут очищать несколько блюд одновременно; но учтите, что вы все равно можете подождать там или вернуться в свою комнату, независимо от того, как они работают.
Надеюсь это поможет
источник
1. Я читаю, что последовательные очереди создаются и используются для выполнения задач одну за другой. Однако что произойдет, если: - • Я создаю последовательную очередь • Я использую dispatch_async (в только что созданной последовательной очереди) три раза для отправки трех блоков A, B, C
ОТВЕТ : - Все три блока выполняются один за другим. Я создал один пример кода, который помогает понять.
источник