
¡Hola! Mi nombre es Kiryl Famin y soy desarrolladora de iOS.
En este artículo, abordaremos Grand Central Dispatch (GCD) de una vez por todas. Si bien GCD puede parecer obsoleto ahora que existe Swift Modern Concurrency, el código que utiliza este marco seguirá apareciendo durante muchos años, tanto en producción como en entrevistas.
Hoy nos centraremos únicamente en la comprensión fundamental de GCD. Solo examinaremos en detalle los aspectos clave del multithreading, incluida la relación entre colas y subprocesos , un tema que muchos otros artículos tienden a pasar por alto. Con estos conceptos en mente, será más fácil para usted comprender temas como DispatchGroup
, DispatchBarrier
, semáforo, mutex, etc.
Este artículo será útil tanto para principiantes como para desarrolladores experimentados. Intentaré explicar todo en un lenguaje claro, evitando una sobrecarga de términos técnicos.
Hilo : básicamente, un contenedor donde se coloca y ejecuta un conjunto de instrucciones del sistema. De hecho, todo código ejecutable se ejecuta en algún hilo. Distinguimos entre el hilo principal y los hilos de trabajo.
Subprocesamiento múltiple : la capacidad de un sistema de ejecutar varios subprocesos simultáneamente (al mismo tiempo). Esto permite que varias ramas de código se ejecuten en paralelo.
Grand Central Dispatch (GCD) : un marco que facilita el trabajo con subprocesos (aprovechando los beneficios del multihilo). Sus primitivas principales son las tareas y las colas.
Por lo tanto, GCD es una herramienta que facilita la escritura de código que se ejecuta simultáneamente. Un ejemplo sencillo es la descarga de cálculos pesados a un hilo independiente para no interferir con las actualizaciones de la interfaz de usuario en el hilo principal.
Tarea : conjunto de instrucciones agrupadas por el desarrollador. Es importante entender que el desarrollador decide qué código pertenece a una tarea en particular.
Por ejemplo:
print(“GCD”) // a task
let database = Database() let person = Person(age: 23) // also a task database.store(person)
Cola : es el elemento básico de GCD y es el lugar donde el desarrollador coloca las tareas para su ejecución. La cola asume la responsabilidad de distribuir las tareas entre los subprocesos (cada cola tiene acceso al grupo de subprocesos del sistema).
Básicamente, las colas te permiten concentrarte en organizar tu código en tareas en lugar de administrar subprocesos directamente. Cuando envías una tarea a una cola, se ejecutará en un subproceso disponible, que suele ser diferente del que se utilizó para enviar la tarea.
Puedes encontrar versiones mp4 de todos los GIF
Cola principal : cola que se ejecuta únicamente en el subproceso principal. Es serial (hablaremos más sobre esto más adelante).
let mainQueue = DispatchQueue.main
Colas globales: el sistema ofrece cinco colas (una por cada nivel de prioridad) que funcionan simultáneamente.
let globalQueue = DispatchQueue.global()
Colas personalizadas: colas creadas por el desarrollador. El desarrollador elige una de las 5 prioridades y el tipo: serial o concurrente (por defecto, son seriales).
let userQueue = DispatchQueue(label: “com.kirylfamin.concurrent”, attributes: .concurrent).
Calidad de servicio (QoS) : un sistema de prioridades de colas. Cuanto mayor sea la prioridad de la cola en la que se coloca una tarea, más recursos se le asignan. En total, hay cinco niveles de QoS:
.userInteractive
: la prioridad más alta. Se utiliza para tareas que requieren una ejecución inmediata pero que no son adecuadas para ejecutarse en el hilo principal. Por ejemplo, en una aplicación que permite retocar imágenes en tiempo real, el resultado del retoque debe calcularse instantáneamente; sin embargo, si se realiza en el hilo principal, interferiría con las actualizaciones de la interfaz de usuario y el manejo de gestos que siempre ocurren en el hilo principal (por ejemplo, cuando el usuario desliza el dedo sobre el área a retocar, la aplicación debe mostrar instantáneamente el resultado "debajo del dedo"). De esta manera, obtenemos el resultado lo más rápido posible sin sobrecargar el hilo principal.
.userInitiated
: una prioridad para tareas que requieren una respuesta rápida, aunque no tan crítica como las tareas interactivas. Se utiliza normalmente para tareas en las que el usuario entiende que la tarea no se completará de forma instantánea y tendrá que esperar (por ejemplo, una solicitud del servidor).
.default
: la prioridad estándar. Se asigna si el desarrollador no especifica una calidad de servicio al crear una cola, cuando no hay requisitos específicos para la tarea y su prioridad no se puede determinar a partir del contexto (por ejemplo, si invoca una tarea desde una cola con una prioridad .userInitiated, la tarea hereda esa prioridad).
.utility
: una prioridad para las tareas que no requieren una respuesta inmediata del usuario pero que son necesarias para el funcionamiento de la aplicación. Por ejemplo, sincronizar datos con un servidor o escribir un guardado automático en el disco.
.background
: la prioridad más baja. Un ejemplo es la limpieza de caché.
Todas las colas se clasifican como colas seriales o colas concurrentes.
Colas seriales : como su nombre lo indica, son colas en las que las tareas se ejecutan una tras otra. Esto significa que la siguiente tarea comienza solo después de que finaliza la actual .
Colas concurrentes : estas colas permiten que las tareas se ejecuten en paralelo; una nueva tarea comienza tan pronto como se asignan los recursos, independientemente de si las tareas anteriores se completaron. Tenga en cuenta que solo se garantiza el orden de inicio (una tarea puesta en cola antes comenzará antes que una posterior), pero no se garantiza el orden de finalización.
Es importante tener en cuenta que ahora estamos analizando los métodos de ejecución de las tareas en relación con el subproceso que las llama . En otras palabras, la forma en que se llama a una tarea determina cómo se desarrollan los eventos en el subproceso desde el que se envía la tarea a una cola.
async
)Una llamada asincrónica es aquella en la que el hilo que realiza la llamada no está bloqueado, es decir, no espera a que se ejecute la tarea que pone en cola.
DispatchQueue.main.async { print(“A”) } print(“B”)
En este ejemplo, ponemos en cola de forma asincrónica la tarea print("A")
en la cola principal desde el hilo principal (ya que este código no está dentro de ninguna cola específica, se ejecuta en el hilo principal de forma predeterminada). Por lo tanto, no esperamos a que la tarea esté en el hilo principal y continuamos la ejecución de inmediato. En este ejemplo en particular, la tarea print("A")
se pone en cola en la cola principal y luego print("B")
se ejecuta inmediatamente en el hilo principal. Debido a que el hilo principal está ocupado ejecutando el código actual (y las tareas de la cola principal solo se pueden ejecutar en el hilo principal), la tarea actual print("B")
finaliza primero y solo después de que el hilo principal esté libre se ejecuta la tarea print("A")
en cola en la cola principal. El resultado es: BA.
DispatchQueue.global().async { updateData() DispatchQueue.main.async { updateInterface() } Logger.log(.success) } indicateLoading()
Agregamos de forma asincrónica una tarea a la cola global con la prioridad predeterminada del hilo principal, por lo que el hilo que realiza la llamada continúa inmediatamente y llama indicateLoading()
.
Después de un tiempo, el sistema asigna recursos para la tarea y la ejecuta en un hilo de trabajo libre del grupo de hilos, y se llama updateData()
.
La tarea que contiene updateInterface()
se pone en cola de forma asincrónica en la cola principal: el hilo de trabajo que la llama no espera a que se complete y continúa.
Debido a que las tareas se ponen en cola de forma asincrónica, no podemos estar seguros de cuándo se asignarán los recursos. En este caso, no podemos decir con certeza si updateInterface()
(en el hilo principal) o Logger.log(.success)
(en un hilo de trabajo) se ejecutarán primero (tampoco podemos decir con certeza en los pasos 1 y 2: cuál se ejecuta primero, indicateLoading()
en el hilo principal o updateData()
en un hilo de trabajo). Aunque el hilo principal está ocupado manejando actualizaciones de la interfaz de usuario, procesamiento de gestos y otras tareas subyacentes, no obstante siempre recibe la máxima cantidad de recursos del sistema. Por otro lado, los recursos para la ejecución en un hilo de trabajo también se pueden asignar casi de inmediato.
Tenga en cuenta que en esta animación una cola global ejecuta sus tareas en algún hilo de trabajo libre.
sync
)Una llamada sincrónica es aquella en la que el hilo que realiza la llamada se detiene y espera a que se complete la tarea que ha puesto en cola.
let userQueue = DispatchQueue(label: "com.kirylfamin.serial") DispatchQueue.global().async { var account = BankAccount() userQueue.sync { account.balance += 10 } let balance = account.balance print(balance) }
Aquí, desde un subproceso de trabajo que ejecuta una tarea en la cola global, ponemos en cola de manera sincrónica una tarea en una cola personalizada para aumentar el saldo. El subproceso actual se bloquea y espera a que finalice la tarea en cola. Por lo tanto, el saldo se imprime solo después de que la tarea en la cola personalizada complete el incremento.
Nota: En la animación anterior, una cola personalizada ejecuta sus tareas en algún hilo de trabajo libre.
En el contexto de las tareas sincrónicas, es importante hablar sobre el bloqueo, es decir, cuando uno o más subprocesos esperan indefinidamente a que ellos mismos o los demás procedan. El ejemplo más común es llamar a DispatchQueue.main.sync {} desde el subproceso principal.
El hilo principal está ocupado ejecutando la tarea actual, dentro de la cual queremos ejecutar de forma sincrónica algún código. Por lo tanto, la llamada sincrónica bloquea el hilo principal. La tarea se pone en cola en la cola principal, pero no puede iniciarse porque el hilo principal está bloqueado esperando a que finalice la tarea actual, y las tareas en la cola principal solo pueden ejecutarse en el hilo principal. Esto puede ser difícil de visualizar al principio, pero la clave es entender que la tarea en cola con DispatchQueue.main.sync
se convierte en parte de la tarea actual, y la estamos poniendo en cola después de la tarea actual. Como resultado, el hilo espera una parte de la tarea actual que no puede iniciarse porque el hilo está ocupado por la tarea actual.
func printing() { print(“A”) DispatchQueue.main.sync { print(“B”) } print(“C”) }
Tenga en cuenta que print("B")
desde la cola principal no puede ejecutarse porque el hilo principal está bloqueado.
En esta sección, con todo el conocimiento adquirido hasta ahora, abordaremos ejercicios de distinta complejidad: desde bloques de código sencillos que encontrarás en entrevistas hasta desafíos avanzados que pondrán a prueba tu comprensión de la programación concurrente. La pregunta en todas estas tareas es: ¿Qué se imprimirá en la consola?
Recuerde que la cola principal es serial, las colas globales son concurrentes y, a veces, el problema puede incluir colas personalizadas con atributos específicos.
Comenzaremos con tareas de dificultad normal, es decir, aquellas que tienen pocas probabilidades de generar incertidumbre en el resultado. Estas tareas son las que más probablemente aparezcan en las entrevistas; la clave es tomarse el tiempo y analizar el problema con atención.
Puedes encontrar el código completo de todos los ejercicios aquí .
Tarea 1
print(“A”) DispatchQueue.main.async { print(“B”) } print(“C”)
print("A")
.print("B")
se pone en cola de forma asincrónica en la cola principal. Como el hilo principal está ocupado, esta tarea espera en la cola.print("C")
.print("B")
.
Respuesta : ACB
Tarea 2
print(“A”) DispatchQueue.main.async { print(“B”) } DispatchQueue.main.async { print(“C”) } print(“D”)
print("A")
.print("B")
se pone en cola en la cola principal hasta que el subproceso principal esté disponible.print("C")
se pone en cola después de imprimir("B") y también espera.print("B")
.print("C")
.
Respuesta : ADBC
Debo mencionar de inmediato que en algunos ejemplos simplificaré un poco la explicación y omitiré el hecho de que el sistema optimiza la ejecución de llamadas sincrónicas, que discutiremos más adelante.
Tarea 3
print(“A”) DispatchQueue.main.async { // 1 print(“B”) DispatchQueue.main.async { print(“C”) } DispatchQueue.global().sync { print(“D”) } DispatchQueue.main.sync { // 2 print(“E”) } } // 3 print(“F”) DispatchQueue.main.async { print(“G”) }
print("A")
se ejecuta en el hilo principal."F"
.print("G")
se pone en cola en la cola principal después de la tarea anterior (pasos 1 a 3).print("B")
comienza a ejecutarse.print("C")
se coloca en la cola principal (donde la tarea actual aún se está ejecutando y print("G")
la sigue en la cola). Como se agrega de forma asincrónica, no esperamos su ejecución y continuamos de inmediato.print("D")
se pone en cola en la cola global. Como esta llamada es sincrónica, esperamos hasta que la cola global la ejecute (puede ejecutarse en cualquier subproceso de trabajo disponible) antes de continuar.print("E")
se pone en cola en la cola principal. Dado que esta llamada es sincrónica, el subproceso actual debe bloquearse hasta que se complete la tarea. Sin embargo, ya hay tareas en la cola principal y la operación print("E")
se agrega al final, después de ellas. Por lo tanto, esas operaciones deben ejecutarse primero antes de que print("E")
pueda ejecutarse. Pero el subproceso principal aún está ocupado ejecutando la operación actual, por lo que no puede pasar a las siguientes operaciones en cola. Incluso si no hubiera operaciones para imprimir "G"
y "C"
después de la operación actual, el subproceso aún no podría continuar porque la operación actual (pasos 1 a 3) aún no se ha completado."G"
y "C"
.
Respuesta : AFBD
Respuesta alternativa (si la segunda llamada fuera async
): AFBDGCE
Tarea 4
let serialQueue = DispatchQueue(label: “com.kirylfamin.serial”) serialQueue.async { // 1 print(“A”) serialQueue.sync { print(“B”) } print(“C”) } // 2
Una tarea (pasos 1 y 2) se pone en cola de forma asincrónica en una cola serial personalizada (de manera predeterminada, las colas son seriales ya que no usamos el atributo .concurrent
).
"A"
.print("B")
. Como la llamada es sincrónica, el subproceso se bloquea a la espera de su ejecución.print("B")
no puede iniciarse, lo que genera un bloqueo.
Respuesta : A, punto muerto
Este ejemplo muestra que el bloqueo puede ocurrir en cualquier cola serial, ya sea la cola principal o una personalizada.
Tarea 5
Reemplacemos la cola serial de la tarea anterior con una concurrente.
DispatchQueue.global().async { // 1 print("A") DispatchQueue.global().sync { print("B") } print("C") } // 2
"A"
.print("B")
en la misma cola global, que bloquea el hilo de trabajo actual hasta que se complete la tarea.print("B")
en otro subproceso de trabajo."C"
.Respuesta : ABC
Tarea 6
print("A") DispatchQueue.main.async { // 1 print("B") DispatchQueue.main.async { // 2 print("C") DispatchQueue.main.async { // 3 print("D") DispatchQueue.main.sync { print("E") } } // 4 } // 5 DispatchQueue.global().sync { // 6 print("F") DispatchQueue.global().sync { print("G") } } // 7 print("H") } // 8 print("I")
El hilo principal imprime "A"
.
Una tarea asincrónica (pasos 1 a 8) se pone en cola en la cola principal sin bloquear el hilo actual.
El hilo principal continúa e imprime "I"
.
Más tarde, cuando el hilo principal está libre, la tarea puesta en la cola principal comienza a ejecutarse e imprime "B"
.
Otra tarea asincrónica (pasos 2 a 5) se pone en cola en la cola principal, sin bloquear el hilo actual.
Continuando la ejecución en el hilo actual, se realiza un envío sincrónico de la operación 6-7 a la cola global; esto bloquea el hilo actual (principal) hasta que se complete la tarea.
La operación 6–7 comienza a ejecutarse en otro hilo, imprimiendo "F"
.
La operación para print("G")
se envía sincrónicamente a la cola global, bloqueando el hilo de trabajo actual hasta que se complete.
Se imprime "G"
y se desbloquea el hilo de trabajo desde el que se envió esta operación.
La operación 6-7 se completa, desbloqueando el hilo desde el cual fue enviada (el hilo principal) y se imprime "H"
.
Una vez completada la operación 1–2, la ejecución pasa a la siguiente operación en la cola principal (operación 2–5), que comienza e imprime "C"
.
La operación 3-4 se pone en cola en la cola principal sin bloquear el hilo.
Una vez finalizada la operación actual (2–5), se inicia la ejecución de la siguiente operación (3–4), imprimiendo "D"
.
La operación para print("G")
se envía sincrónicamente a la cola principal, bloqueando el hilo actual.
El sistema espera entonces indefinidamente que la operación print("E")
se ejecute en el hilo principal; dado que el hilo está bloqueado, esto genera un punto muerto.
Respuesta : AIBFGHCD, punto muerto
Las tareas de dificultad intermedia implican incertidumbre. Este tipo de problemas también se dan en las entrevistas, aunque raramente.
Tarea 7
DispatchQueue.global().async { print("A") } DispatchQueue.global().async { print("B") }
print("A")
se pone en cola de forma asincrónica en la cola global, sin bloquear el hilo actual.print("B")
. En este caso particular, la siguiente tarea se agrega primero a la cola y solo entonces se asignan recursos a la cola global. Esto sucede porque al hilo principal se le asignan la mayor cantidad de recursos y la siguiente operación en el hilo principal es muy liviana (simplemente la operación de agregar una tarea) y, en la práctica, ocurre más rápido que la asignación de recursos en la cola global. Analizaremos los escenarios opuestos en la siguiente sección.print("B")
se pone en cola en la cola global."A"
podría comenzar antes que la "B"
, no podemos garantizar el orden porque la impresión no es una operación atómica (el momento en que aparece la salida en la consola está cerca del final de la operación).
Respuesta : (AB)
Los paréntesis indican que las letras pueden aparecer en cualquier orden: AB o BA.
Tarea 8
print("A") DispatchQueue.main.async { print("B") } DispatchQueue.global().async { print("C") }
En este caso, solo podemos estar seguros de que "A" se imprime primero. No podemos determinar con precisión si la tarea en la cola principal o la de la cola global se ejecutará más rápido.
Respuesta : A(BC)
Tarea 9
DispatchQueue.global(qos: .userInteractive).async { print(“A”) } DispatchQueue.main.async { // 1 print(“B”) }
y
DispatchQueue.global(qos: .userInteractive).async { print(“A”) } print(“B”) // 1
Por un lado, en ambos casos print("B")
se ejecuta en el hilo principal. Además, no podemos determinar exactamente cuándo se asignarán recursos a la cola global, por lo que, en teoría, "A"
podría imprimirse inmediatamente antes de alcanzar el punto marcado // 1 en el hilo principal. Sin embargo, en la práctica, la primera tarea siempre se imprime como AB, mientras que la segunda se imprime como BA. Esto se debe a que en el primer caso, print("B")
se ejecuta al menos en la siguiente iteración de RunLoop del hilo principal (o unas cuantas iteraciones más tarde), mientras que en el segundo caso, print("B")
está programado para ejecutarse en la iteración actual de RunLoop en el hilo principal. Sin embargo, no podemos garantizar el orden.
Respuesta para ambas tareas: (AB)
Tarea 10
print("A") DispatchQueue.global().async { print("B") DispatchQueue.global().async { print("C") } print("D") }
Está claro que el comienzo de la salida es "AB"
. Después de poner en cola print("C")
, no podemos determinar exactamente cuándo se le asignarán recursos; esta tarea puede ejecutarse antes o después de print("D")
. Esto también sucede a veces en la práctica.
Respuesta : AB(CD)
Tarea 11
let serialQueue = DispatchQueue(label: “com.kirylfamin.serial”, qos: .userInteractive) DispatchQueue.main.async { print(“A”) serialQueue.async { print(“B”) } print(“C”) }
Nuevamente, no podemos determinar con precisión cuándo se asignarán recursos para print("B") en la cola personalizada. En la práctica, dado que el hilo principal tiene la máxima prioridad, "C" generalmente se imprime antes que "B", aunque esto no está garantizado.
Respuesta : A(BC)
Tarea 12
DispatchQueue.global().async { print("A") } print("B") sleep(1) print("C")
Aquí, es obvio que la salida será BAC porque el segundo de suspensión garantiza que la cola global tenga tiempo suficiente para asignar recursos. Mientras el hilo principal está bloqueado por la suspensión (lo que no debería hacer en producción), print("A")
se ejecuta en otro hilo.
Respuesta : BAC
Tarea 13
DispatchQueue.main.async { print("A") } print("B") sleep(1) print("C")
En este caso, dado que print("A")
está en cola en la cola principal, solo se puede ejecutar en el hilo principal. Sin embargo, el hilo principal continúa ejecutando el código: imprime "B"
, luego se queda inactivo y luego imprime "C"
. Solo después de eso, RunLoop puede ejecutar la tarea en cola.
Respuesta : BCA
Es poco probable que te encuentres con estos problemas en las entrevistas, pero comprenderlos te ayudará a comprender mejor el MCD.
La clase Counter se utiliza aquí únicamente para semántica de referencia:
final class Counter { var count = 0 }
Tarea 14
let counter = Counter() DispatchQueue.global().async { DispatchQueue.main.async { print(counter.count) } for _ in (0..<100) { // 1 counter.count += 1 } }
Aquí, se puede imprimir cualquier número entre 0 y 100, dependiendo de lo ocupado que esté el hilo principal. Como sabemos, no podemos predecir exactamente cuándo la tarea asincrónica obtendrá recursos: puede suceder antes, durante o después del bucle en el hilo de trabajo.
Respuesta : 0-100
Tarea 15
DispatchQueue.global(qos: .userInitiated).async { print(“A”) } DispatchQueue.global(qos: .userInteractive).async { print(“B”) }
La calidad de servicio no garantiza que la cola con mayor prioridad reciba recursos más rápido, aunque iOS intentará hacerlo. En la práctica, el resultado aquí es (AB).
Respuesta : (AB)
Tarea 16
var count = 0 DispatchQueue.global(qos: . userInitiated).async { for _ in 0..<1000 { count += 1 } print(“A”) } DispatchQueue.global(qos: .userInteractive).async { for _ in 0..<1000 { count += 1 } print(“B”) }
Como no podemos saber qué ejecución comienza primero, incluso entre 1000 operaciones no podemos determinar qué tarea se completará más rápido.
Respuesta : (AB)
Tarea 16.2
¿Cuál es el resultado suponiendo que las operaciones comienzan a ejecutarse simultáneamente?
Dado que a la cola .userInteractive se le asignan más recursos, en el lapso de 1000 operaciones la ejecución en esa cola siempre finalizará más rápido.
Respuesta : BA
Tarea 17
Utilizando un enfoque similar, podemos modificar cualquier tarea con incertidumbre de la sección anterior (por ejemplo, Tarea 12):
let counter = Counter() let serialQueue = DispatchQueue(label: “com.kirylfamin.serial”, qos: .userInteractive) DispatchQueue.main.async { serialQueue.async { print(counter.count) } for _ in 0..<100 { counter.count += 1 } }
Se puede imprimir cualquier número entre 0 y 100. El hecho de que se pueda imprimir 0 confirma que en la Tarea 12 no podemos garantizar que la salida de "C"
siempre ocurrirá antes que "B"
, ya que esencialmente nada ha cambiado, solo que el bucle consume un poco más de recursos que una impresión (tenga en cuenta que simplemente iniciar el bucle, incluso antes de su ejecución, en la práctica ha resultado en una incertidumbre total).
Respuesta : 0-100
Tarea 18
DispatchQueue.global(qos: .userInitiated).async { print(“A”) } print(“B”) DispatchQueue.global(qos: .userInteractive).async { print(“C”) }
Aquí ocurre una situación similar. En teoría, print("A")
podría ejecutarse más rápido que print("B")
(si reemplaza print("B")
con algo un poco más pesado). En la práctica, "B"
siempre se imprime primero. Sin embargo, el hecho de que ejecutemos print("B")
antes de poner en cola print("C")
aumenta en gran medida la probabilidad de que "A"
se imprima antes que "C"
, ya que el tiempo adicional que se dedica a print("B")
en el hilo principal suele ser suficiente para que la cola .userInitiated obtenga recursos y ejecute print("A")
. No obstante, esto no está garantizado y, a veces "C"
puede imprimirse más rápido. Por lo tanto, en teoría hay una incertidumbre total; en la práctica, tiende a ser B(CA).
Respuesta : (BCA)
Tarea 19
DispatchQueue.global().sync { print(Thread.current) }
La documentación de sincronización establece lo siguiente:
“Como optimización del rendimiento, esta función ejecuta bloques en el hilo actual siempre que sea posible, con una excepción: los bloques enviados a la cola de despacho principal siempre se ejecutan en el hilo principal”.
Esto significa que, por motivos de optimización, las llamadas sincrónicas pueden ejecutarse en el mismo hilo desde el que se llamaron (con excepción de main.sync
: las tareas que lo utilizan siempre se ejecutan en el hilo principal). Por lo tanto, se imprime el hilo actual (principal).
Respuesta : hilo principal
Tarea 20
DispatchQueue.global().sync { // 1 print(“A”) DispatchQueue.main.sync { print(“B”) } print(“C”) }
Solo se imprime "A"
porque se produce un bloqueo. Debido a la optimización, la tarea (etiquetada como 1) comienza a ejecutarse en el hilo principal y, luego, al llamar a main.sync
, se produce un bloqueo.
Respuesta : A, punto muerto
Tarea 21
DispatchQueue.main.async { print("A") DispatchQueue.global().sync { print("B") } print("C") }
La optimización hace que la tarea print("B")
no se ponga en cola, sino que se "inserte" en el hilo de ejecución actual. Por lo tanto, el código:
DispatchQueue.global().sync { print("B") }
se convierte en equivalente a:
print(“B”)
Respuesta : ABC
De estas tareas se desprende claramente que debes utilizar main.sync con mucho cuidado: solo cuando estés seguro de que la llamada no se realiza desde el hilo principal.
En este artículo, nos centramos en los conceptos básicos del multithreading en iOS (threads, tareas y colas) y sus interrelaciones. Exploramos cómo GCD gestiona la ejecución de tareas en las colas principales, globales y personalizadas, y analizamos las diferencias entre la ejecución en serie y la ejecución simultánea. Además, examinamos las distinciones críticas entre el envío de tareas sincrónicas (sync) y asincrónicas (async), destacando cómo estos enfoques afectan el orden y el tiempo de ejecución del código. Dominar estos conceptos básicos es esencial para crear aplicaciones estables y con capacidad de respuesta y para evitar errores comunes como los bloqueos.
Espero que este artículo te haya resultado útil. Si algo no te ha quedado claro, no dudes en ponerte en contacto conmigo para que te lo explique sin coste alguno en Telegram: @kfamyn .
sync
: https://developer.apple.com/documentation/dispatch/dispatchqueue/sync(execute:)-3segw