Параллельные и последовательные очереди в GCD

117

Я изо всех сил пытаюсь полностью понять параллельные и последовательные очереди в GCD. У меня есть некоторые проблемы, и я надеюсь, что кто-нибудь ответит мне четко и по существу.

  1. Я читаю, что последовательные очереди создаются и используются для выполнения задач одну за другой. Однако что произойдет, если:

    • Я создаю последовательную очередь
    • Я использую dispatch_async(в только что созданной последовательной очереди) три раза для отправки трех блоков A, B, C

    Будут ли выполнены три блока:

    • в порядке A, B, C, потому что очередь последовательная

      ИЛИ

    • одновременно (в одно и то же время в параллельных потоках), потому что я использовал отправку ASYNC
  2. Я читаю, что могу использовать dispatch_syncв параллельных очередях для выполнения блоков один за другим. В таком случае, ПОЧЕМУ вообще существуют последовательные очереди, поскольку я всегда могу использовать параллельную очередь, в которой я могу СИНХРОННО отправлять столько блоков, сколько захочу?

    Спасибо за хорошее объяснение!

Богдан Александру
источник
Простой хороший предварительный вопрос: синхронизация отправки и асинхронизация
Дорогая,

Ответы:

216

Простой пример: у вас есть блок, выполнение которого занимает минуту. Вы добавляете его в очередь из основного потока. Давайте посмотрим на четыре случая.

  • async - параллельный: код выполняется в фоновом потоке. Управление немедленно возвращается в основной поток (и пользовательский интерфейс). Блок не может предполагать, что это единственный блок, работающий в этой очереди.
  • async - serial: код выполняется в фоновом потоке. Управление немедленно возвращается в основной поток. Блок может предполагать, что это единственный блок, работающий в этой очереди.
  • sync - concurrent: код выполняется в фоновом потоке, но основной поток ожидает его завершения, блокируя любые обновления пользовательского интерфейса. Блок не может предполагать, что это единственный блок, работающий в этой очереди (я мог бы добавить еще один блок, используя async за несколько секунд до этого).
  • sync - serial: код выполняется в фоновом потоке, но основной поток ожидает его завершения, блокируя любые обновления пользовательского интерфейса. Блок может предполагать, что это единственный блок, работающий в этой очереди.

Очевидно, вы не стали бы использовать ни один из двух последних для длительных процессов. Обычно вы видите это, когда пытаетесь обновить пользовательский интерфейс (всегда в основном потоке) из того, что может выполняться в другом потоке.

Стивен Дарлингтон
источник
14
Итак, вы говорите мне, что: (1) тип очереди (conc или serial) - это ЕДИНСТВЕННЫЙ элемент, который определяет, будут ли задачи выполняться по порядку или параллельно ;; (2) тип отправки (синхронный или асинхронный) только говорит, идет ли выполнение ИЛИ не переходит ли к следующей инструкции? Я имею в виду, что если я отправлю задачу SYNC, код будет блокироваться до завершения этой задачи, независимо от того, в какой очереди он выполняется?
Богдан Александру
13
@BogdanAlexandru Правильно. Очередь диктует политику выполнения, а не то, как вы ставите блок в очередь. Sync ожидает завершения блока, async - нет.
Jano
2
@swiftBUTCHER До определенного момента да. При создании очереди вы можете указать максимальное количество потоков. Если вы добавите меньше задач, они будут выполняться параллельно. Более того, некоторые задачи будут оставаться в очереди до тех пор, пока не освободится емкость.
Стивен Дарлингтон,
2
@PabloA., Основной поток - это последовательная очередь, поэтому на самом деле есть только два случая. В остальном это точно так же. Async возвращается немедленно (и блок, вероятно, выполняется в конце текущего цикла выполнения). Главный Гоча, если вы делаете синхронизацию от главного потока к основному потоку, в этом случае вы получите в тупик.
Стивен Дарлингтон
1
@ShauketSheikh Нет. Основной поток - это последовательная очередь, но не все последовательные очереди являются основным потоком. В четвертом пункте основной поток будет блокироваться, ожидая, пока другой поток завершит свою работу. Если бы последовательная очередь была основным потоком, вы бы зашли в тупик.
Стивен Дарлингтон
122

Вот несколько экспериментов , которые я сделал , чтобы заставить меня понять о них serial, concurrentочередями с Grand Central Dispatch.

 func doLongAsyncTaskInSerialQueue() {

   let serialQueue = DispatchQueue(label: "com.queue.Serial")
      for i in 1...5 {
        serialQueue.async {

            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
    }
}

При использовании async в GCD задача будет выполняться в другом потоке (кроме основного). Асинхронность означает выполнение следующей строки, не дожидаясь выполнения блока, в результате чего основной поток и основная очередь не блокируются. Начиная с его последовательной очереди, все выполняются в том порядке, в котором они добавляются в последовательную очередь. Задачи, выполняемые последовательно, всегда выполняются по очереди одним потоком, связанным с Очередью.

func doLongSyncTaskInSerialQueue() {
    let serialQueue = DispatchQueue(label: "com.queue.Serial")
    for i in 1...5 {
        serialQueue.sync {
            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
    }
}

При использовании синхронизации в GCD задача может выполняться в основном потоке. Синхронизация запускает блок в заданной очереди и ожидает его завершения, что приводит к блокировке основного потока или основной очереди. Поскольку основной очереди необходимо дождаться завершения отправленного блока, основной поток будет доступен для обработки блоков из очередей, отличных от очереди. Таким образом, существует вероятность того, что код, выполняющийся в фоновой очереди, может фактически выполняться в основном потоке. Поскольку его последовательная очередь, все они выполняются в порядке добавления (FIFO).

func doLongASyncTaskInConcurrentQueue() {
    let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
    for i in 1...5 {
        concurrentQueue.async {
            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
        print("\(i) executing")
    }
}

При использовании асинхронного режима в GCD задача будет выполняться в фоновом потоке. Асинхронность означает выполнение следующей строки, не дожидаясь выполнения блока, в результате чего основной поток не блокируется. Помните, что в параллельной очереди задачи обрабатываются в том порядке, в котором они добавляются в очередь, но с разными потоками, прикрепленными к очереди. Помните, что они не должны завершать задачу, так как порядок их добавления в очередь меняется каждый раз, когда потоки создаются автоматически. Задачи выполняются параллельно. При достижении большего значения (maxConcurrentOperationCount) некоторые задачи будут вести себя как последовательные, пока поток не освободится.

func doLongSyncTaskInConcurrentQueue() {
  let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
    for i in 1...5 {
        concurrentQueue.sync {
            if Thread.isMainThread{
                print("task running in main thread")
            }else{
                print("task running in background thread")
            }
            let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
            let _ = try! Data(contentsOf: imgURL)
            print("\(i) completed downloading")
        }
        print("\(i) executed")
    }
}

При использовании синхронизации в GCD задача может выполняться в основном потоке. Синхронизация запускает блок в заданной очереди и ожидает его завершения, что приводит к блокировке основного потока или основной очереди. Поскольку основной очереди необходимо дождаться завершения отправленного блока, основной поток будет доступен для обработки блоков из очередей, отличных от очереди. Таким образом, существует вероятность того, что код, выполняющийся в фоновой очереди, действительно может выполняться в основном потоке. Поскольку это параллельная очередь, задачи могут не завершаться в том порядке, в котором они были добавлены в очередь. Но при синхронной работе это так, хотя они могут обрабатываться разными потоками. Итак, он ведет себя как последовательная очередь.

Вот краткое изложение этих экспериментов

Помните, что с помощью GCD вы только добавляете задачу в очередь и выполняете задачу из этой очереди. Очередь отправляет вашу задачу либо в основном, либо в фоновом потоке, в зависимости от того, является ли операция синхронной или асинхронной. Типы очередей: Последовательная, Параллельная, Основная очередь отправки. Вся задача, которую вы выполняете, по умолчанию выполняется из Главной очереди отправки. Для вашего приложения уже существует четыре предопределенных глобальных параллельных очереди и одна основная очередь (DispatchQueue.main). вы также можете вручную создать свою очередь и выполнять задачи из этой очереди.

Задача, связанная с пользовательским интерфейсом, всегда должна выполняться из основного потока путем отправки задачи в основную очередь. Утилита для коротких рук - это DispatchQueue.main.sync/asyncтогда, когда связанные с сетью / тяжелые операции всегда должны выполняться асинхронно, независимо от того, какой поток вы используете либо в основном, либо в фоновом

РЕДАКТИРОВАТЬ: Однако есть случаи, когда вам нужно выполнять операции сетевых вызовов синхронно в фоновом потоке без замораживания пользовательского интерфейса (например, обновить токен OAuth и подождать, удастся ли он удастся или нет). Вам необходимо обернуть этот метод внутри асинхронной операции. операции выполняются в порядке и без блокировки основного потока.

func doMultipleSyncTaskWithinAsynchronousOperation() {
    let concurrentQueue = DispatchQueue(label: "com.queue.Concurrent", attributes: .concurrent)
    concurrentQueue.async {
        let concurrentQueue = DispatchQueue.global(qos: DispatchQoS.QoSClass.default)
        for i in 1...5 {
            concurrentQueue.sync {
                let imgURL = URL(string: "https://upload.wikimedia.org/wikipedia/commons/0/07/Huge_ball_at_Vilnius_center.jpg")!
                let _ = try! Data(contentsOf: imgURL)
                print("\(i) completed downloading")
            }
            print("\(i) executed")
        }
    }
}

РЕДАКТИРОВАТЬ РЕДАКТИРОВАТЬ: вы можете посмотреть демонстрационное видео здесь

LC 웃
источник
Отличная демонстрация .... следующая строка не дожидается выполнения блока, в результате чего основной поток не блокируется, поэтому, если вы используете точки останова в фоновом потоке, он перейдет к }нему, потому что он действительно не выполняется в этот момент
Honey
@ Этот ленивый iOS-парень 웃 Я до сих пор не понимаю разницы между async concurrent и async serial. Каковы последствия использования того и другого. Оба они работают в фоновом режиме, не мешая пользовательскому интерфейсу. И зачем вообще использовать синхронизацию? Это не синхронизация всего кода. один за другим?
eonist
1
@GitSyncApp, вы можете посмотреть видео здесь
Аниш Параджули 웃
@ Этот ленивый iOS-парень: спасибо за это. Я разместил на Slack Swift-lang. Было бы 👌 Если бы вы могли сделать еще одно о DispatchGroup и DispatchWorkItem. : D
eonist 03
Я проверил свой последний, concurrentQueue.syncиз doLongSyncTaskInConcurrentQueue()функции, он выводит основную нить, Task will run in different threadкажется , не так.
gabbler
54

Во-первых, важно знать разницу между потоками и очередями и то, что на самом деле делает GCD. Когда мы используем очереди отправки (через GCD), мы действительно ставим в очередь, а не распределяем потоки. Инфраструктура Dispatch была разработана специально для того, чтобы увести нас от многопоточности, поскольку Apple признает, что «реализация правильного решения для многопоточности [может] стать чрезвычайно трудным, а иногда и невозможным». Следовательно, для одновременного выполнения задач (задач, которые мы не хотим, чтобы пользовательский интерфейс замораживался), все, что нам нужно сделать, это создать очередь этих задач и передать ее GCD. И GCD обрабатывает все связанные потоки. Поэтому все, что мы на самом деле делаем, - это выстраиваемся в очередь.

Второе, что нужно сразу знать, - что это за задача. Задача - это весь код в этом блоке очереди (не в очереди, потому что мы можем добавлять вещи в очередь все время, но в пределах закрытия, где мы добавили его в очередь). Задачу иногда называют блоком, а блок - задачей (но они чаще называются задачами, особенно в сообществе Swift). И независимо от того, сколько или мало кода, весь код в фигурных скобках считается одной задачей:

serialQueue.async {
    // this is one task
    // it can be any number of lines with any number of methods
}
serialQueue.async {
    // this is another task added to the same queue
    // this queue now has two tasks
}

И очевидно, что упоминание о том, что «одновременный» означает «одновременно с другими», а «последовательный» означает одно за другим (никогда в одно и то же время). Сериализовать что-либо или поместить что-то в последовательный формат, означает просто выполнить это от начала до конца в порядке слева направо, сверху вниз, без прерывания.

Есть два типа очередей: последовательные и параллельные, но все очереди параллельны друг относительно друга . Тот факт, что вы хотите запускать любой код «в фоновом режиме», означает, что вы хотите запускать его одновременно с другим потоком (обычно с основным потоком). Следовательно, все очереди отправки, последовательные или параллельные, выполняют свои задачи одновременно по отношению к другим очередям . Любая сериализация, выполняемая очередями (последовательными очередями), имеет отношение только к задачам в этой единственной [последовательной] очереди отправки (как в приведенном выше примере, где две задачи находятся в одной последовательной очереди; эти задачи будут выполняться одна за другой). другой, никогда одновременно).

ПОСЛЕДОВАТЕЛЬНЫЕ Очереди (часто известные как частные очереди отправки) гарантируют выполнение задач по одной от начала до конца в том порядке, в котором они были добавлены в эту конкретную очередь. Это единственная гарантия сериализации при обсуждении очередей отправки.- что определенные задачи в определенной последовательной очереди выполняются последовательно. Однако последовательные очереди могут выполняться одновременно с другими последовательными очередями, если они являются отдельными очередями, потому что, опять же, все очереди параллельны друг относительно друга. Все задачи выполняются в разных потоках, но не каждая задача гарантированно запускается в одном потоке (не важно, но интересно знать). И в iOS-фреймворке нет готовых последовательных очередей, вы должны их создать. Частные (неглобальные) очереди по умолчанию являются последовательными, поэтому для создания последовательной очереди:

let serialQueue = DispatchQueue(label: "serial")

Вы можете сделать его параллельным через его свойство attribute:

let concurrentQueue = DispatchQueue(label: "concurrent", attributes: [.concurrent])

Но на этом этапе, если вы не добавляете какие-либо другие атрибуты в частную очередь, Apple рекомендует вам просто использовать одну из их готовых к работе глобальных очередей (которые работают одновременно). Внизу этого ответа вы увидите другой способ создания последовательных очередей (с использованием свойства target), который Apple рекомендует делать (для более эффективного управления ресурсами). Но пока достаточно маркировки.

CONCURRENT QUEUES (часто называемые глобальными очередями отправки) могут выполнять задачи одновременно; Тем не менее, задачи гарантированно запускаются в том порядке, в котором они были добавлены в эту конкретную очередь, но, в отличие от последовательных очередей, очередь не ждет завершения первой задачи перед запуском второй задачи. Задачи (как и в случае с последовательными очередями) выполняются в разных потоках, и (как и в случае с последовательными очередями) не каждая задача гарантированно запускается в одном потоке (не важно, но интересно знать). Платформа iOS поставляется с четырьмя готовыми к использованию параллельными очередями. Вы можете создать параллельную очередь, используя приведенный выше пример или одну из глобальных очередей Apple (что обычно рекомендуется):

let concurrentQueue = DispatchQueue.global(qos: .default)

СОХРАНЕНИЕ ЦИКЛОВ: Очереди отправки являются объектами с подсчетом ссылок, но вам не нужно сохранять и освобождать глобальные очереди, потому что они являются глобальными и, таким образом, сохранение и освобождение игнорируются. Вы можете получить доступ к глобальным очередям напрямую, не назначая их свойству.

Есть два способа диспетчеризации очередей: синхронно и асинхронно.

SYNC DISPATCHING означает, что поток, в котором была отправлена ​​очередь (вызывающий поток), приостанавливается после отправки очереди и ожидает завершения выполнения задачи в этом блоке очереди перед возобновлением. Для синхронной отправки:

DispatchQueue.global(qos: .default).sync {
    // task goes in here
}

ASYNC DISPATCHING означает, что вызывающий поток продолжает работать после отправки очереди и не ждет, пока задача в этом блоке очереди завершит выполнение. Для асинхронной отправки:

DispatchQueue.global(qos: .default).async {
    // task goes in here
}

Теперь можно подумать, что для последовательного выполнения задачи следует использовать последовательную очередь, а это не совсем так. Для последовательного выполнения нескольких задач следует использовать последовательную очередь, но все задачи (изолированные сами по себе) выполняются последовательно. Рассмотрим этот пример:

whichQueueShouldIUse.syncOrAsync {
    for i in 1...10 {
        print(i)
    }
    for i in 1...10 {
        print(i + 100)
    }
    for i in 1...10 {
        print(i + 1000)
    }
}

Независимо от того, как вы настраиваете (последовательный или параллельный) или отправляете (синхронизирующий или асинхронный) эту очередь, эта задача всегда будет выполняться последовательно. Третий цикл никогда не будет выполняться перед вторым циклом, а второй цикл никогда не будет выполняться перед первым циклом. Это верно для любой очереди с любой диспетчеризацией. Это когда вы вводите несколько задач и / или очередей, где действительно вступают в игру последовательный интерфейс и параллелизм.

Рассмотрим эти две очереди, одну последовательную и одну параллельную:

let serialQueue = DispatchQueue(label: "serial")
let concurrentQueue = DispatchQueue.global(qos: .default)

Скажем, мы отправляем две параллельные очереди в асинхронном режиме:

concurrentQueue.async {
    for i in 1...5 {
        print(i)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
101
2
102
103
3
104
4
105
5

Их вывод беспорядочный (как и ожидалось), но обратите внимание, что каждая очередь последовательно выполняла свою задачу. Это самый простой пример параллелизма - две задачи, выполняемые одновременно в фоновом режиме в одной очереди. А теперь сделаем первый серийник:

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

101
1
2
102
3
103
4
104
5
105

Разве первая очередь не должна выполняться последовательно? Было (и было второе). Что бы еще ни произошло в фоновом режиме, очередь не касается. Мы сказали последовательной очереди выполняться последовательно, и она сделала ... но мы дали ей только одну задачу. Теперь давайте дадим ему две задачи:

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
serialQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
2
3
4
5
101
102
103
104
105

И это самый простой (и единственно возможный) пример сериализации - две задачи, выполняющиеся последовательно (одна за другой) в фоновом режиме (для основного потока) в одной очереди. Но если мы сделаем их двумя отдельными последовательными очередями (потому что в приведенном выше примере это одна и та же очередь), их вывод снова будет беспорядочным:

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
serialQueue2.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
101
2
102
3
103
4
104
5
105

Именно это я имел в виду, когда сказал, что все очереди параллельны друг относительно друга. Это две последовательные очереди, выполняющие свои задачи одновременно (потому что это отдельные очереди). Очередь не знает других очередей и не заботится о них. Теперь давайте вернемся к двум последовательным очередям (из той же очереди) и добавим третью очередь, параллельную:

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
serialQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 1000)
    }
}

1
2
3
4
5
101
102
103
104
105
1001
1002
1003
1004
1005

Это своего рода неожиданность, почему параллельная очередь ожидала завершения последовательных очередей, прежде чем она была выполнена? Это не параллелизм. Ваша игровая площадка может показывать другой результат, но моя показала это. И он показал это, потому что приоритет моей параллельной очереди был недостаточно высок, чтобы GCD мог выполнить свою задачу раньше. Поэтому, если я сохраню все то же самое, но изменю QoS глобальной очереди (ее качество обслуживания, которое является просто уровнем приоритета очереди) let concurrentQueue = DispatchQueue.global(qos: .userInteractive), то результат будет таким, как ожидалось:

1
1001
1002
1003
2
1004
1005
3
4
5
101
102
103
104
105

Две последовательные очереди выполняли свои задачи последовательно (как и ожидалось), а параллельная очередь выполняла свою задачу быстрее, потому что ей был присвоен высокий уровень приоритета (высокий QoS или качество обслуживания).

Две параллельные очереди, как и в нашем первом примере печати, показывают беспорядочную распечатку (как и ожидалось). Чтобы заставить их печатать аккуратно в последовательном порядке, нам нужно было бы создать для них одну и ту же последовательную очередь (также один и тот же экземпляр этой очереди, а не одну и ту же этикетку) . Затем каждая задача выполняется последовательно по отношению к другой. Однако другой способ заставить их печатать по порядку - сохранить их одновременно, но изменить их метод отправки:

concurrentQueue.sync {
    for i in 1...5 {
        print(i)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
2
3
4
5
101
102
103
104
105

Помните, что диспетчеризация синхронизации означает только то, что вызывающий поток ожидает завершения задачи в очереди, прежде чем продолжить. Предостережение здесь, очевидно, заключается в том, что вызывающий поток замораживается до завершения первой задачи, что может или не может быть так, как вы хотите, чтобы пользовательский интерфейс выполнялся.

И именно по этой причине мы не можем делать следующее:

DispatchQueue.main.sync { ... }

Это единственная возможная комбинация очередей и методов диспетчеризации, которую мы не можем выполнить - синхронная диспетчеризация в основной очереди. И это потому, что мы просим основную очередь «заморозиться», пока мы не выполним задачу в фигурных скобках ... которую мы отправили в основную очередь, которую мы просто заморозили. Это называется тупиком. Чтобы увидеть это в действии на детской площадке:

DispatchQueue.main.sync { // stop the main queue and wait for the following to finish
    print("hello world") // this will never execute on the main queue because we just stopped it
}
// deadlock

И последнее, о чем стоит упомянуть, - это ресурсы. Когда мы даем очереди задачу, GCD находит доступную очередь из своего пула, управляемого изнутри. Что касается написания этого ответа, для каждого qos доступно 64 очереди. Может показаться, что это много, но они могут быть быстро использованы, особенно сторонними библиотеками, особенно фреймворками баз данных. По этой причине у Apple есть рекомендации по управлению очередью (упомянутые в ссылках ниже); одно существо:

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

Для этого вместо того, чтобы создавать их, как мы делали раньше (что вы все еще можете), Apple рекомендует создавать такие последовательные очереди:

let serialQueue = DispatchQueue(label: "serialQueue", qos: .default, attributes: [], autoreleaseFrequency: .inherit, target: .global(qos: .default))

Для дальнейшего чтения рекомендую следующее:

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

BSOD
источник
7

Если я правильно понимаю, как работает GCD, я думаю, что есть два типа DispatchQueue, serialи concurrentв то же время есть два способа DispatchQueueдиспетчеризации своих задач: назначенный closure, первый asyncи другой sync. Вместе они определяют, как на самом деле выполняется закрытие (задача).

Я обнаружил это serialи concurrentимел ввиду, сколько потоков может использовать очередь, serialозначает один, тогда как concurrentозначает много. И , syncи asyncозначает , что задание будет выполняться на какой поток, поток вызывающего или нить , лежащая в основе этой очереди, syncзначит работать на потоке вызывающего абонента в то время как asyncсредство запуска на основной нити.

Ниже приведен экспериментальный код, который можно запускать на игровой площадке Xcode.

PlaygroundPage.current.needsIndefiniteExecution = true
let cq = DispatchQueue(label: "concurrent.queue", attributes: .concurrent)
let cq2 = DispatchQueue(label: "concurent.queue2", attributes: .concurrent)
let sq = DispatchQueue(label: "serial.queue")

func codeFragment() {
  print("code Fragment begin")
  print("Task Thread:\(Thread.current.description)")
  let imgURL = URL(string: "http://stackoverflow.com/questions/24058336/how-do-i-run-asynchronous-callbacks-in-playground")!
  let _ = try! Data(contentsOf: imgURL)
  print("code Fragment completed")
}

func serialQueueSync() { sq.sync { codeFragment() } }
func serialQueueAsync() { sq.async { codeFragment() } }
func concurrentQueueSync() { cq2.sync { codeFragment() } }
func concurrentQueueAsync() { cq2.async { codeFragment() } }

func tasksExecution() {
  (1...5).forEach { (_) in
    /// Using an concurrent queue to simulate concurent task executions.
    cq.async {
      print("Caller Thread:\(Thread.current.description)")
      /// Serial Queue Async, tasks run serially, because only one thread that can be used by serial queue, the underlying thread of serial queue.
      //serialQueueAsync()
      /// Serial Queue Sync, tasks run serially, because only one thread that can be used by serial queue,one by one of the callers' threads.
      //serialQueueSync()
      /// Concurrent Queue Async, tasks run concurrently, because tasks can run on different underlying threads
      //concurrentQueueAsync()
      /// Concurrent Queue Sync, tasks run concurrently, because tasks can run on different callers' thread
      //concurrentQueueSync()
    }
  }
}
tasksExecution()

Надеюсь, это может быть полезно.

Кит
источник
7

Мне нравится думать об этом, используя эту метафору (вот ссылка на исходное изображение):

Папе понадобится помощь

Представьте, что ваш папа моет посуду, а вы только что выпили стакан содовой. Вы приносите стакан отцу, чтобы он вымыл, ставя его рядом с другой посудой.

Теперь ваш папа сам моет посуду, поэтому ему придется мыть посуду одну за другой: ваш папа представляет собой последовательную очередь .

Но вам не очень интересно стоять там и смотреть, как все убирают. Итак, вы бросаете стакан и возвращаетесь в свою комнату: это называется асинхронной отправкой . Ваш папа может сообщить вам, а может и не дать вам знать, когда он закончит, но важно то, что вы не ждете, пока стекло будет очищено; ты возвращаешься в свою комнату, чтобы заняться детскими вещами.

А теперь давайте предположим, что вы все еще хотите пить и хотите выпить воды на том же стакане, который вам нравится больше всего, и вы действительно хотите вернуть его, как только он будет вымыт. Итак, вы стоите и смотрите, как ваш папа моет посуду, пока не вымоет ваша. Это отправка синхронизации , поскольку вы заблокированы, пока ожидаете завершения задачи.

И, наконец, допустим, ваша мама решает помочь вашему папе и присоединяется к нему, мыть посуду. Теперь очередь становится параллельной, так как они могут очищать несколько блюд одновременно; но учтите, что вы все равно можете подождать там или вернуться в свою комнату, независимо от того, как они работают.

Надеюсь это поможет

Юнус Недим Мехел
источник
3

1. Я читаю, что последовательные очереди создаются и используются для выполнения задач одну за другой. Однако что произойдет, если: - • Я создаю последовательную очередь • Я использую dispatch_async (в только что созданной последовательной очереди) три раза для отправки трех блоков A, B, C

ОТВЕТ : - Все три блока выполняются один за другим. Я создал один пример кода, который помогает понять.

let serialQueue = DispatchQueue(label: "SampleSerialQueue")
//Block first
serialQueue.async {
    for i in 1...10{
        print("Serial - First operation",i)
    }
}

//Block second
serialQueue.async {
    for i in 1...10{
        print("Serial - Second operation",i)
    }
}
//Block Third
serialQueue.async {
    for i in 1...10{
        print("Serial - Third operation",i)
    }
}
CrazyPro007
источник