Estoy luchando por comprender completamente las colas simultáneas y en serie en GCD. Tengo algunos problemas y espero que alguien pueda responderme claramente y en el punto.
Estoy leyendo que las colas en serie se crean y utilizan para ejecutar tareas una tras otra. Sin embargo, ¿qué sucede si:
- Creo una cola en serie
- Utilizo
dispatch_async
(en la cola de serie que acabo de crear) tres veces para enviar tres bloques A, B, C
¿Se ejecutarán los tres bloques:
en orden A, B, C porque la cola es serial
O
- al mismo tiempo (al mismo tiempo en subprocesos paralelos) porque utilicé el envío ASYNC
Estoy leyendo que puedo usar
dispatch_sync
en colas concurrentes para ejecutar bloques uno tras otro. En ese caso, ¿POR QUÉ existen las colas en serie, ya que siempre puedo usar una cola concurrente en la que puedo enviar SINCRÓNICAMENTE tantos bloques como quiera?¡Gracias por cualquier buena explicación!
ios
multithreading
concurrency
grand-central-dispatch
Bogdan Alexandru
fuente
fuente
Respuestas:
Un ejemplo sencillo: tienes un bloque que tarda un minuto en ejecutarse. Lo agrega a una cola desde el hilo principal. Veamos los cuatro casos.
Obviamente, no usaría ninguno de los dos últimos para procesos de larga ejecución. Normalmente lo ve cuando intenta actualizar la interfaz de usuario (siempre en el hilo principal) de algo que puede estar ejecutándose en otro hilo.
fuente
Aquí hay un par de experimentos que he hecho de hacerme entender acerca de estos
serial
,concurrent
colas conGrand Central Dispatch
.Aquí hay un resumen de estos experimentos.
Recuerde que al usar GCD solo está agregando tareas a la cola y realizando tareas desde esa cola. La cola distribuye su tarea en el hilo principal o en segundo plano dependiendo de si la operación es sincrónica o asincrónica. Los tipos de colas son Serial, Concurrente, Cola de despacho principal. Toda la tarea que realiza se realiza de forma predeterminada desde la cola de despacho principal. Ya hay cuatro colas simultáneas globales predefinidas para que las use su aplicación y una cola principal (DispatchQueue.main). también puede crear manualmente su propia cola y realizar tareas desde esa cola.
La tarea relacionada con la interfaz de usuario siempre debe realizarse desde el subproceso principal enviando la tarea a la cola
DispatchQueue.main.sync/async
principal.EDITAR: Sin embargo, hay casos en los que necesita realizar operaciones de llamadas de red sincrónicamente en un hilo en segundo plano sin congelar la interfaz de usuario (egrefreshing OAuth Token y esperar si tiene éxito o no) .Debe envolver ese método dentro de una operación asincrónica. las operaciones se ejecutan en el orden y sin bloquear el hilo principal.
EDITAR EDITAR: puede ver el video de demostración aquí
fuente
}
porque realmente no se está ejecutando en ese momentoconcurrentQueue.sync
dedoLongSyncTaskInConcurrentQueue()
la función, se imprime hilo principal,Task will run in different thread
no parece ser cierto.Primero, es importante conocer la diferencia entre subprocesos y colas y lo que realmente hace GCD. Cuando usamos colas de despacho (a través de GCD), realmente estamos haciendo cola, no subprocesos. El marco de Dispatch fue diseñado específicamente para alejarnos del subproceso, ya que Apple admite que "implementar una solución de subprocesamiento correcta [puede] volverse extremadamente difícil, si no [a veces] imposible de lograr". Por lo tanto, para realizar tareas al mismo tiempo (tareas que no queremos que congelen la interfaz de usuario), todo lo que tenemos que hacer es crear una cola de esas tareas y entregársela a GCD. Y GCD maneja todos los subprocesos asociados. Por lo tanto, todo lo que realmente estamos haciendo es hacer cola.
Lo segundo que hay que saber de inmediato es qué es una tarea. Una tarea es todo el código dentro de ese bloque de cola (no dentro de la cola, porque podemos agregar cosas a una cola todo el tiempo, pero dentro del cierre donde lo agregamos a la cola). En ocasiones, una tarea se denomina bloque y, a veces, un bloqueo se denomina tarea (pero se conocen más comúnmente como tareas, especialmente en la comunidad Swift). Y no importa cuánto o poco código, todo el código dentro de las llaves se considera una sola tarea:
Y es obvio mencionar que concurrente simplemente significa al mismo tiempo con otras cosas y en serie significa uno tras otro (nunca al mismo tiempo). Serializar algo, o poner algo en serie, solo significa ejecutarlo de principio a fin en su orden de izquierda a derecha, de arriba a abajo, sin interrupciones.
Hay dos tipos de colas, seriales y concurrentes, pero todas las colas son concurrentes entre sí . El hecho de que desee ejecutar cualquier código "en segundo plano" significa que desea ejecutarlo simultáneamente con otro hilo (normalmente el hilo principal). Por lo tanto, todas las colas de despacho, en serie o simultáneas, ejecutan sus tareas al mismo tiempo que otras colas . Cualquier serialización realizada por colas (por colas en serie), solo tiene que ver con las tareas dentro de esa única cola de despacho [serial] (como en el ejemplo anterior donde hay dos tareas dentro de la misma cola serial; esas tareas se ejecutarán una después de el otro, nunca simultáneamente).
LAS COLAS DE SERIE (a menudo conocidas como colas de despacho privadas) garantizan la ejecución de las tareas de una en una, de principio a fin, en el orden en que se agregaron a esa cola específica. Esta es la única garantía de serialización en cualquier parte del debate sobre las colas de envío.- que las tareas específicas dentro de una cola de serie específica se ejecutan en serie. Sin embargo, las colas seriales pueden ejecutarse simultáneamente con otras colas seriales si son colas separadas porque, nuevamente, todas las colas son concurrentes entre sí. Todas las tareas se ejecutan en subprocesos distintos, pero no se garantiza que todas las tareas se ejecuten en el mismo subproceso (no es importante, pero es interesante saberlo). Y el marco de iOS no viene con colas seriales listas para usar, debe crearlas. Las colas privadas (no globales) son seriales por defecto, así que para crear una cola serial:
Puede hacerlo concurrente a través de su propiedad de atributo:
Pero en este punto, si no está agregando ningún otro atributo a la cola privada, Apple recomienda que solo use una de sus colas globales listas para usar (que son todas concurrentes). En la parte inferior de esta respuesta, verá otra forma de crear colas en serie (utilizando la propiedad de destino), que es como Apple recomienda hacerlo (para una gestión de recursos más eficiente). Pero por ahora, etiquetarlo es suficiente.
LAS COLAS CONCURRENTES (a menudo conocidas como colas de despacho global) pueden ejecutar tareas simultáneamente; Sin embargo, se garantiza que las tareas se inicien en el orden en que se agregaron a esa cola específica, pero a diferencia de las colas en serie, la cola no espera a que finalice la primera tarea antes de comenzar la segunda. Las tareas (como con las colas en serie) se ejecutan en subprocesos distintos y (como con las colas en serie) no se garantiza que todas las tareas se ejecuten en el mismo subproceso (no es importante, pero es interesante saberlo). Y el marco de iOS viene con cuatro colas simultáneas listas para usar. Puede crear una cola simultánea usando el ejemplo anterior o usando una de las colas globales de Apple (que generalmente se recomienda):
Hay dos formas de distribuir colas: de forma sincrónica y asincrónica.
SYNC DISPATCHING significa que el subproceso donde se envió la cola (el subproceso que llama) se detiene después de enviar la cola y espera a que la tarea en ese bloque de cola termine de ejecutarse antes de reanudar. Para enviar sincrónicamente:
ASYNC DISPATCHING significa que el subproceso que llama continúa ejecutándose después de enviar la cola y no espera a que la tarea en ese bloque de cola termine de ejecutarse. Para enviar de forma asincrónica:
Ahora, uno podría pensar que para ejecutar una tarea en serie, se debe usar una cola de serie, y eso no es exactamente correcto. Para ejecutar múltiples tareas en serie, se debe usar una cola de serie, pero todas las tareas (aisladas por sí mismas) se ejecutan en serie. Considere este ejemplo:
No importa cómo configure (en serie o concurrente) o envíe (sincronizada o asincrónica) esta cola, esta tarea siempre se ejecutará en serie. El tercer ciclo nunca se ejecutará antes del segundo ciclo y el segundo ciclo nunca se ejecutará antes del primer ciclo. Esto es cierto en cualquier cola que utilice cualquier envío. Es cuando introduces múltiples tareas y / o colas donde la serie y la concurrencia realmente entran en juego.
Considere estas dos colas, una en serie y otra simultánea:
Digamos que enviamos dos colas simultáneas en asincrónico:
Su salida está desordenada (como se esperaba) pero observe que cada cola ejecuta su propia tarea en serie. Este es el ejemplo más básico de simultaneidad: dos tareas que se ejecutan al mismo tiempo en segundo plano en la misma cola. Ahora hagamos la primera serie:
¿No se supone que la primera cola se ejecuta en serie? Lo fue (y también lo fue el segundo). Cualquier otra cosa que haya sucedido en el fondo no es motivo de preocupación para la cola. Le dijimos a la cola de serie que se ejecutara en serie y lo hizo ... pero solo le dimos una tarea. Ahora démosle dos tareas:
Y este es el ejemplo más básico (y único posible) de serialización: dos tareas que se ejecutan en serie (una tras otra) en segundo plano (en el hilo principal) en la misma cola. Pero si los hicimos dos colas en serie separadas (porque en el ejemplo anterior son la misma cola), su salida se vuelve a mezclar:
Y esto es lo que quise decir cuando dije que todas las colas son concurrentes entre sí. Se trata de dos colas en serie que ejecutan sus tareas al mismo tiempo (porque son colas independientes). Una cola no conoce ni se preocupa por otras colas. Ahora volvamos a dos colas en serie (de la misma cola) y agreguemos una tercera cola, una concurrente:
Eso es algo inesperado, ¿por qué la cola concurrente esperó a que las colas seriales terminaran antes de ejecutarse? Eso no es concurrencia. Su patio de recreo puede mostrar un resultado diferente, pero el mío mostró esto. Y mostró esto porque la prioridad de mi cola concurrente no era lo suficientemente alta como para que GCD ejecutara su tarea antes. Entonces, si mantengo todo igual pero cambio la QoS de la cola global (su calidad de servicio, que es simplemente el nivel de prioridad de la cola)
let concurrentQueue = DispatchQueue.global(qos: .userInteractive)
, entonces el resultado es el esperado:Las dos colas en serie ejecutaron sus tareas en serie (como se esperaba) y la cola concurrente ejecutó su tarea más rápido porque se le dio un nivel de prioridad alto (una QoS alta o calidad de servicio).
Dos colas simultáneas, como en nuestro primer ejemplo de impresión, muestran una impresión desordenada (como se esperaba). Para que se impriman de forma ordenada en serie, tendríamos que hacer que ambos tengan la misma cola de serie (también la misma instancia de esa cola, no solo la misma etiqueta) . Luego, cada tarea se ejecuta en serie con respecto a la otra. Sin embargo, otra forma de hacer que se impriman en serie es mantenerlos simultáneamente, pero cambiar su método de envío:
Recuerde, el envío de sincronización solo significa que el subproceso que realiza la llamada espera hasta que se complete la tarea en la cola antes de continuar. La advertencia aquí, obviamente, es que el hilo de llamada se congela hasta que se completa la primera tarea, que puede ser o no la forma en que desea que funcione la interfaz de usuario.
Y es por esta razón que no podemos hacer lo siguiente:
Ésta es la única combinación posible de colas y métodos de despacho que no podemos realizar: despacho sincrónico en la cola principal. Y eso es porque le pedimos a la cola principal que se congele hasta que ejecutemos la tarea dentro de las llaves ... que enviamos a la cola principal, que simplemente congelamos. Esto se llama punto muerto. Para verlo en acción en un patio de recreo:
Una última cosa a mencionar son los recursos. Cuando le asignamos una tarea a una cola, GCD encuentra una cola disponible en su grupo administrado internamente. En cuanto a la redacción de esta respuesta, hay 64 colas disponibles por qos. Puede parecer mucho, pero se pueden consumir rápidamente, especialmente en bibliotecas de terceros, en particular en los marcos de bases de datos. Por esta razón, Apple tiene recomendaciones sobre la gestión de colas (mencionadas en los enlaces a continuación); uno de ellos:
Para hacer esto, en lugar de crearlos como lo hicimos antes (que todavía puedes), Apple recomienda crear colas en serie como esta:
Para leer más, recomiendo lo siguiente:
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
fuente
Si entiendo correctamente cómo funciona GCD, creo que hay dos tipos de
DispatchQueue
,serial
yconcurrent
al mismo tiempo, hay dos formas deDispatchQueue
despachar sus tareas, la asignadaclosure
, la primera esasync
y la otrasync
. Esos juntos determinan cómo se ejecuta realmente el cierre (tarea).Encontré eso
serial
y meconcurrent
refiero a cuántos subprocesos puede usar esa cola,serial
significa uno, mientras queconcurrent
significa muchos. Ysync
yasync
significa que la tarea se ejecutará en el que el hilo, el hilo de la persona que llama o el hilo subyacente a esa cola,sync
los medios se ejecutan en el hilo de la persona que llama mientras queasync
los medios se ejecutan en el hilo subyacente.El siguiente es un código experimental que se puede ejecutar en Xcode playground.
Espero que pueda ser de ayuda.
fuente
Me gusta pensar esto usando esta metáfora (aquí está el enlace a la imagen original):
Imaginemos que tu papá está lavando los platos y tú acabas de tomar un vaso de refresco. Le llevas el vaso a tu papá para que lo limpie y lo colocas junto al otro plato.
Ahora tu papá está lavando los platos solo, así que tendrá que hacerlo uno por uno: tu papá aquí representa una cola en serie .
Pero no está realmente interesado en quedarse allí y ver cómo se limpia. Entonces, dejas caer el vaso y regresas a tu habitación: esto se llama envío asíncrono . Es posible que tu padre te avise o no una vez que haya terminado, pero lo importante es que no estás esperando a que limpien el vidrio; vuelves a tu habitación para hacer, ya sabes, cosas de niños.
Ahora supongamos que todavía tiene sed y quiere tener un poco de agua en ese mismo vaso que resulta ser su favorito, y realmente lo quiere de vuelta tan pronto como esté limpio. Entonces, te quedas ahí y ves a tu papá lavar los platos hasta que el tuyo esté listo. Este es un envío de sincronización , ya que está bloqueado mientras espera a que finalice la tarea.
Y finalmente digamos que tu mamá decide ayudar a tu papá y se une a él para lavar los platos. Ahora la cola se convierte en una cola concurrente ya que pueden limpiar varios platos al mismo tiempo; pero tenga en cuenta que aún puede decidir esperar allí o volver a su habitación, independientemente de cómo funcionen.
Espero que esto ayude
fuente
1. Estoy leyendo que las colas en serie se crean y utilizan para ejecutar tareas una tras otra. Sin embargo, ¿qué sucede si: - • Creo una cola en serie • Utilizo dispatch_async (en la cola en serie que acabo de crear) tres veces para enviar tres bloques A, B, C
RESPUESTA : - Los tres bloques se ejecutaron uno tras otro. He creado un código de muestra que ayuda a comprender.
fuente