В чём разница между последовательной (serial) и параллельной (concurrent) очередью в GCD?

Ответ

Разница заключается в порядке выполнения задач и использовании потоков.

Характеристика Serial Queue (Последовательная) Concurrent Queue (Параллельная)
Выполнение задач Строго по порядку, одна за другой. Одновременно, насколько позволяет система.
Потоки Использует один поток (но не обязательно всегда один и тот же системный поток). Использует несколько потоков из пула.
Главное преимущество Гарантированная потокобезопасность для общих ресурсов. Производительность за счёт параллелизма.
Типичное применение Синхронизация доступа к общим данным, порядок операций. Выполнение независимых, ресурсоёмких задач.

Практический пример:

let serialQueue = DispatchQueue(label: "com.example.serial")
let concurrentQueue = DispatchQueue(label: "com.example.concurrent", attributes: .concurrent)

// Задачи на Serial Queue выполнятся суммарно за ~3 секунды.
serialQueue.async { sleep(1); print("Serial Task 1 done") }
serialQueue.async { sleep(1); print("Serial Task 2 done") } // Ждёт окончания Task 1
serialQueue.async { sleep(1); print("Serial Task 3 done") } // Ждёт окончания Task 2

// Задачи на Concurrent Queue выполнятся суммарно за ~1 секунду.
concurrentQueue.async { sleep(1); print("Concurrent Task 1 done") }
concurrentQueue.async { sleep(1); print("Concurrent Task 2 done") } // Запускается сразу
concurrentQueue.async { sleep(1); print("Concurrent Task 3 done") } // Запускается сразу

Ключевой вывод:

  • Быстрее для независимых задач — concurrent очередь.
  • Безопаснее для операций с общим состоянием — serial очередь (или concurrent с барьерами .barrier). Выбор зависит от конкретной задачи.

Ответ 18+ 🔞

Давай разберём эту дичь про очереди, а то у меня уже глаза на лоб лезут от этих терминов. Представь, что ты в столовой стоишь.

Serial Queue (последовательная) — это как одна, ёпта, касса с бабкой-бухгалтершей, которая считает на деревянных счётах. Пока она твоему соседу не выбила чек за борщ с котлетой, ты даже спросить не можешь, где тут соль. Одна задача за другой, строго по талонам. Пиздец как медленно, зато никто никому в тарелку не плюнет — порядок железный.

Concurrent Queue (параллельная) — это уже раздолбанный «Макдональдс» с пятью окошками. Три кассира, два курьера, и все орут: «Номер 57, ваш доширак!». Задачи лезут одновременно, как тараканы из щели. Быстро? Ебать да! Но если все начнут хватать одни и те же наггетсы — начнётся драка, потому что синхронизации ноль.

Вот тебе живой пример, чтобы вообще всё стало ясно, как божий день:

let serialQueue = DispatchQueue(label: "com.example.serial") // Одна бабка-кассирша
let concurrentQueue = DispatchQueue(label: "com.example.concurrent", attributes: .concurrent) // Пять окошек в макдаке

// На последовательной очереди задачи будут ползти, как черепахи в сиропе. ~3 секунды.
serialQueue.async { sleep(1); print("Serial Task 1 done") } // Первый получил чек
serialQueue.async { sleep(1); print("Serial Task 2 done") } // Ждёт, пока первый уйдёт
serialQueue.async { sleep(1); print("Serial Task 3 done") } // И этот ждёт, блядь

// На параллельной — всё взлетит почти одновременно. ~1 секунда.
concurrentQueue.async { sleep(1); print("Concurrent Task 1 done") } // Поехали!
concurrentQueue.async { sleep(1); print("Concurrent Task 2 done") } // Уже бежит!
concurrentQueue.async { sleep(1); print("Concurrent Task 3 done") } // И этот стартанул!

Итог, коротко и на пальцах:

  • Concurrent — когда задачи друг про друга не знают и не ебут. Кидай всё в котёл и греби быстрее.
  • Serial — когда у тебя общая база данных, массив или та самая последняя пельмешка в холодильнике. Тут нужен порядок, а то начнётся «я первый взял!» — «нет, я!», и привет, гонка данных, ёпта.

А если в concurrent очереди вдруг надо что-то сделать эксклюзивно (например, записать в общий файл), то есть волшебный флаг .barrier. Это как крикнуть: «ВСЕ НАХУЙ ЗАМЕРЛИ, Я ЗАПИСЫВАЮ!». После этого все опять побегут.

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