Una DispatchQueue es el objeto al que se le envía trabajo en GCD. No se le envían hilos, se le envían closures (bloques de código), y es la cola la que decide cómo y cuándo ejecutarlas.
FIFO: orden de entrega, no necesariamente orden real de inicio
Una DispatchQueue es una cola FIFO (first in, first out): GCD entrega los closures para ejecución en el mismo orden en que se despacharon. En una cola serial esa garantía coincide con el orden real de inicio, porque solo hay un carril de ejecución — un closure no puede empezar hasta que el anterior termine.
En una cola concurrente esa coincidencia se rompe. GCD entrega los closures a hilos independientes en orden FIFO, pero una vez entregados, es el scheduler del sistema operativo — no GCD — quien decide cuándo cada hilo ejecuta su primera instrucción. Un closure despachado después puede imprimir su primera línea antes que uno despachado antes, sobre todo si requiere menos trabajo de arranque (por ejemplo, sync puede reutilizar el hilo que hace el llamado en lugar de pedir uno nuevo del pool, mientras que cada async sí necesita adquirirlo). El artículo de DQ Concurrente — sync muestra un caso real de esto.
El orden de finalización tampoco está garantizado en una cola concurrente — eso depende de si la cola es serial o concurrente, lo cual se cubre en los próximos dos artículos. Lo único que GCD garantiza siempre, en cualquier tipo de cola, es el orden de entrega FIFO descrito arriba.
Prioridad: Quality of Service (QoS)
Cada tarea que se despacha tiene una prioridad asociada, expresada como DispatchQoS. El sistema usa esa prioridad para decidir qué tarea atender primero cuando varias compiten por CPU:
-
.userInteractive: la más alta. No significa "corre enmain" ni "bloquea la interfaz" — el bloqueo de la UI solo ocurre si el trabajo corre en el hilo principal, y eso lo decide la cola, no el QoS. Es para trabajo cuya tardanza haría que la interfaz se sienta congelada (animaciones, o un cálculo en background cuyo resultado alimenta de inmediato a la UI). -
.userInitiated: trabajo que el usuario está esperando activamente (abrir un documento, procesar el resultado de un tap). -
.default: prioridad por defecto cuando no se especifica una explícita. -
.utility: trabajo de larga duración que no necesita resultado inmediato (descargas, procesamiento de datos, con barra de progreso visible). -
.background: la más baja. Trabajo que el usuario no percibe (sincronización en segundo plano, indexación). El QoS es una señal relativa de prioridad, no una garantía de velocidad. El scheduler del sistema la usa para decidir, cuando hay más hilos con trabajo pendiente que núcleos disponibles, a cuáles asignarles tiempo de CPU primero. Si todas las tareas del proceso declaran.userInteractive, el scheduler pierde la información que necesita para diferenciarlas: todas piden el mismo nivel de servicio, así que terminan repartiéndose los mismos núcleos por turnos (time-slicing), igual que pasaría si todas tuvieran.default. El resultado medible es que ninguna tarea termina antes por estar marcada.userInteractive— el código de animación que sí necesita esa prioridad ahora compite por CPU contra trabajo que no la necesitaba, con las mismas probabilidades de ser interrumpido.
Tipos de cola
GCD expone tres formas de obtener una DispatchQueue:
// 1. Cola principal: serial, ligada al hilo principal (UI)
let main = DispatchQueue.main
// 2. Colas globales: concurrentes, provistas por el sistema, una por cada QoS
let global = DispatchQueue.global(qos: .userInitiated)
// 3. Cola propia: serial por defecto
let custom = DispatchQueue(label: "dev.goyes.tareas")
// 3b. Cola propia, hecha concurrente explícitamente
let customConcurrent = DispatchQueue(label: "dev.goyes.tareas-concurrentes", attributes: .concurrent)
Cola principal (DispatchQueue.main). Es serial y siempre ejecuta en el hilo principal. Todo lo que toca UI debe pasar por aquí.
Colas globales (DispatchQueue.global(qos:)). Son concurrentes y las administra el sistema. No se crean, se obtienen — hay una por cada nivel de QoS, y se comparten con el resto de la app (y potencialmente con otros procesos del sistema). La firma es global(qos: DispatchQoS.QoSClass = .default): el parámetro tiene un valor por defecto, así que DispatchQueue.global() sin argumentos no significa "sin QoS" — equivale explícitamente a DispatchQueue.global(qos: .default).
Colas propias (DispatchQueue(label:)). Las crea el desarrollador. Por defecto son seriales; se vuelven concurrentes solo si se especifica attributes: .concurrent. El label no afecta el comportamiento — sirve únicamente para identificar la cola en herramientas de depuración como Instruments.
Nota: en el código de arriba, solo la cola global muestra explícitamente un QoS (qos: .userInitiated). No es que DispatchQueue.main y DispatchQueue(label:) carezcan de QoS — DispatchQueue.main corre siempre con la prioridad más alta del sistema y no se puede reconfigurar, y DispatchQueue(label:) sí acepta un parámetro qos: en su inicializador completo (init(label:qos:attributes:autoreleaseFrequency:target:)), simplemente no es necesario todavía para lo que cubre este artículo. Cómo asignar QoS a una cola propia se explica en un artículo posterior.
Lo que viene
Ya con cola serial vs. concurrente identificadas, lo que falta es ver cómo cambia el comportamiento al despachar trabajo de forma síncrona o asíncrona en cada una. Eso son los siguientes cuatro artículos.
Top comments (0)