Colas concurrentes vs seriales en GCD

117

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.

  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 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
  2. Estoy leyendo que puedo usar dispatch_syncen 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!

Bogdan Alexandru
fuente
Una simple buena sincronización de envío de
Cariño

Respuestas:

216

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.

  • async - concurrente: el código se ejecuta en un hilo de fondo. El control vuelve inmediatamente al hilo principal (y la interfaz de usuario). El bloque no puede asumir que es el único bloque que se ejecuta en esa cola.
  • async - serial: el código se ejecuta en un hilo de fondo. El control vuelve inmediatamente al hilo principal. El bloque puede asumir que es el único bloque que se ejecuta en esa cola.
  • sync - concurrent: el código se ejecuta en un subproceso en segundo plano, pero el subproceso principal espera a que termine, bloqueando cualquier actualización de la interfaz de usuario. El bloque no puede asumir que es el único bloque que se ejecuta en esa cola (podría haber agregado otro bloque usando async unos segundos antes)
  • sync - serial: el código se ejecuta en un hilo en segundo plano pero el hilo principal espera a que termine, bloqueando cualquier actualización de la interfaz de usuario. El bloque puede asumir que es el único bloque que se ejecuta en esa cola.

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.

Stephen Darlington
fuente
14
Entonces me está diciendo que: (1) el tipo de cola (conc o serial) es el ÚNICO elemento que decide si las tareas se ejecutan en orden o en paralelo ;; (2) ¿el tipo de envío (sincronizado o asíncrono) solo dice si la ejecución va O no va a la siguiente instrucción? Quiero decir, si envío una tarea SYNC, el código se bloqueará hasta que las tareas finalicen, sin importar en qué cola se ejecute.
Bogdan Alexandru
13
@BogdanAlexandru Correcto. La cola dicta la política de ejecución, no cómo pone en cola el bloque. Sync espera a que se complete el bloqueo, async no.
Jano
2
@swiftBUTCHER Hasta cierto punto, sí. Cuando crea una cola, puede especificar el número máximo de subprocesos. Si agrega menos tareas, se ejecutarán en paralelo. Con más que eso, algunas tareas permanecerán en cola hasta que haya capacidad disponible.
Stephen Darlington
2
@PabloA., El hilo principal es una cola en serie, por lo que en realidad solo hay dos casos. Más allá de eso, es exactamente lo mismo. Async regresa inmediatamente (y el bloque probablemente se ejecuta al final del ciclo de ejecución actual). El Gotcha principal es si lo hace de sincronización desde el hilo principal para el hilo principal, en cuyo caso se obtiene un punto muerto.
Stephen Darlington
1
@ShauketSheikh No. El hilo principal es una cola en serie, pero no todas las colas en serie son el hilo principal. En el cuarto punto, el hilo principal se bloquearía, esperando que otro hilo hiciera su trabajo. Si la cola de serie fuera el hilo principal, obtendría un interbloqueo.
Stephen Darlington
122

Aquí hay un par de experimentos que he hecho de hacerme entender acerca de estos serial, concurrentcolas con 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")
        }
    }
}

La tarea se ejecutará en un hilo diferente (que no sea el hilo principal) cuando use async en GCD. Async significa ejecutar la siguiente línea, no espere hasta que el bloque se ejecute, lo que da como resultado que no bloquee el hilo principal y la cola principal. Desde su cola en serie, todas se ejecutan en el orden en que se agregan a la cola en serie. Las tareas ejecutadas en serie siempre se ejecutan una a la vez por el único hilo asociado con la cola.

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")
        }
    }
}

La tarea puede ejecutarse en el hilo principal cuando usa la sincronización en GCD. Sync ejecuta un bloque en una cola determinada y espera a que se complete, lo que da como resultado el bloqueo del hilo principal o la cola principal. Dado que la cola principal necesita esperar hasta que se complete el bloque enviado, el hilo principal estará disponible para procesar bloques de colas distintas de la Por lo tanto, existe la posibilidad de que el código que se ejecuta en la cola de fondo se esté ejecutando en el subproceso principal. Desde su cola en serie, todos se ejecutan en el orden en que se agregan (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")
    }
}

La tarea se ejecutará en segundo plano cuando use async en GCD. Async significa ejecutar la siguiente línea, no espere hasta que el bloque se ejecute, lo que da como resultado que no bloquee el hilo principal. Recuerde que en la cola concurrente, las tareas se procesan en el orden en que se agregan a la cola, pero con diferentes subprocesos adjuntos a la cola. Recuerde que no se supone que deben terminar la tarea en el orden en que se agregan a la cola. El orden de la tarea difiere cada vez que los subprocesos se crean como necesariamente automáticamente. La tarea se ejecuta en paralelo. Con más de eso (maxConcurrentOperationCount) se alcanza, algunas tareas se comportarán como una serie hasta que un hilo sea libre.

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")
    }
}

La tarea puede ejecutarse en el hilo principal cuando usa la sincronización en GCD. Sync ejecuta un bloque en una cola determinada y espera a que se complete, lo que da como resultado el bloqueo del hilo principal o la cola principal. Dado que la cola principal necesita esperar hasta que se complete el bloque enviado, el hilo principal estará disponible para procesar bloques de colas distintas de la cola principal Por lo tanto, existe la posibilidad de que el código que se ejecuta en la cola en segundo plano se esté ejecutando en el hilo principal. Debido a su cola simultánea, es posible que las tareas no finalicen en el orden en que se agregan a la cola. Pero con el funcionamiento síncrono lo hace, aunque pueden ser procesados ​​por diferentes subprocesos. Entonces, se comporta como si fuera la cola serial.

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/asyncprincipal.

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.

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")
        }
    }
}

EDITAR EDITAR: puede ver el video de demostración aquí

LC 웃
fuente
Gran demostración ... en la siguiente línea, no espere hasta que el bloque se ejecute, lo que da como resultado que no bloquee el hilo principal, es por eso que si usa puntos de interrupción en un hilo en segundo plano, saltará al }porque realmente no se está ejecutando en ese momento
Honey
@ Ese tipo perezoso de iOS 웃 Todavía no entiendo la diferencia entre async concurrente y async serial. ¿Cuál es la implicación de usar ambos. Ambos se ejecutan en segundo plano sin perturbar la interfaz de usuario. ¿Y por qué usarías la sincronización? No todo el código está sincronizado. ¿uno después del otro?
eonista
1
@GitSyncApp puede ver el video aquí
Anish Parajuli 웃
@ Ese tipo perezoso de iOS 웃: gracias por hacer eso. Publiqué en slack swift-lang. Sería 👌 Si pudiera hacer uno sobre DispatchGroup y DispatchWorkItem también. : D
eonista
He probado el último de ellos, el concurrentQueue.syncde doLongSyncTaskInConcurrentQueue()la función, se imprime hilo principal, Task will run in different threadno parece ser cierto.
charlatán
54

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:

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
}

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:

let serialQueue = DispatchQueue(label: "serial")

Puede hacerlo concurrente a través de su propiedad de atributo:

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

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):

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

RESISTENTE AL CICLO DE RETENCIÓN: las colas de envío son objetos contados por referencias, pero no es necesario retener y liberar colas globales porque son globales y, por lo tanto, se ignora la retención y liberación. Puede acceder a las colas globales directamente sin tener que asignarlas a una propiedad.

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:

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

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:

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

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:

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)
    }
}

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:

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

Digamos que enviamos dos colas simultáneas en asincrónico:

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

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:

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

¿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:

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

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:

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

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:

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

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:

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

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:

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

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:

DispatchQueue.main.sync { ... }

É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:

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

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:

En lugar de crear colas simultáneas privadas, envíe tareas a una de las colas de despacho simultáneas globales. Para tareas en serie, establezca el destino de su cola en serie en una de las colas simultáneas globales. De esa manera, puede mantener el comportamiento serializado de la cola mientras minimiza el número de colas separadas que crean subprocesos.

Para hacer esto, en lugar de crearlos como lo hicimos antes (que todavía puedes), Apple recomienda crear colas en serie como esta:

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

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

bsod
fuente
7

Si entiendo correctamente cómo funciona GCD, creo que hay dos tipos de DispatchQueue, serialy concurrental mismo tiempo, hay dos formas de DispatchQueuedespachar sus tareas, la asignada closure, la primera es asyncy la otra sync. Esos juntos determinan cómo se ejecuta realmente el cierre (tarea).

Encontré eso serialy me concurrentrefiero a cuántos subprocesos puede usar esa cola, serialsignifica uno, mientras que concurrentsignifica muchos. Y syncy asyncsignifica que la tarea se ejecutará en el que el hilo, el hilo de la persona que llama o el hilo subyacente a esa cola, synclos medios se ejecutan en el hilo de la persona que llama mientras que asynclos medios se ejecutan en el hilo subyacente.

El siguiente es un código experimental que se puede ejecutar en Xcode playground.

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()

Espero que pueda ser de ayuda.

Keith
fuente
7

Me gusta pensar esto usando esta metáfora (aquí está el enlace a la imagen original):

Papá va a necesitar ayuda

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

Yunus Nedim Mehel
fuente
3

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.

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
fuente