Pregunta Colas concurrentes vs serie en GCD


Estoy luchando por comprender completamente las colas concurrentes y en serie en GCD. Tengo algunos problemas y espero que alguien me pueda responder claramente y en el momento.

  1. Estoy leyendo que las colas en serie se crean y se usan para ejecutar las tareas una después de la otra. Sin embargo, ¿qué ocurre si:

    • Creo una cola en serie
    • yo suelo dispatch_async (en la cola serie que acabo de crear) tres veces para despachar tres bloques A, B, C

    ¿Se ejecutarán los tres bloques?

    • en orden A, B, C porque la cola es serial

      O

    • concurrentemente (al mismo tiempo en hilos parralel) porque utilicé el envío ASYNC
  2. Estoy leyendo que puedo usar dispatch_sync en colas concurrentes para ejecutar bloques uno después del otro. En ese caso, ¿POR QUÉ existen colas en serie, ya que siempre puedo usar una cola concurrente donde puedo enviar SYNCHRONOUSLY todos los bloques que quiera?

    Gracias por cualquier buena explicación!


75
2017-10-04 10:47


origen


Respuestas:


Un ejemplo simple: tienes un bloque que tarda un minuto en ejecutarse. Usted lo agrega a una cola del 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 a la IU). El bloque no puede suponer que es el único bloque que se ejecuta en esa cola
  • async - serial: el código se ejecuta en una cadena de fondo. El control vuelve inmediatamente al hilo principal. El bloque poder supongamos que es el único bloque que se ejecuta en esa cola
  • sincronización: concurrente: el código se ejecuta en un subproceso en segundo plano, pero el subproceso principal espera a que finalice, bloqueando las actualizaciones de la interfaz de usuario. El bloque no puede suponer 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 subproceso en segundo plano, pero el subproceso principal espera a que termine, bloqueando las actualizaciones de la interfaz de usuario. El bloque poder supongamos 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.


138
2017-10-04 11:12



Aquí hay un par de experimentos que he hecho para hacerme entender sobre estos serial, concurrent colas 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 se ejecute el bloque, lo que no bloquea el hilo principal y la cola principal.       Desde su cola en serie, todos se ejecutan en el orden en que se agregan a la cola serie. Las tareas ejecutadas en serie siempre se ejecutan de a una 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 sincronización en GCD. Sync ejecuta un bloque en una cola determinada y espera que se complete, lo que resulta en el bloqueo del hilo principal o la cola principal. Dado que la cola principal debe esperar hasta que el bloque enviado finalice, el hilo principal estará disponible para procesar bloques de colas que no sean cola principal. Por lo tanto, existe la posibilidad de que el código que se está ejecutando en la cola de fondo se esté ejecutando en el hilo 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 el hilo de fondo cuando use async en GCD. Async significa ejecutar la siguiente línea, no espere hasta que se ejecute el bloque, lo que resulta en un bloqueo del hilo principal.       Recuerde que en la cola concurrente, la tarea se procesa en el orden en que se agregan a la cola, pero con diferentes subprocesos conectados al   cola. Recuerde que no se supone que terminen la tarea como la orden   se agregan a la cola. El orden de la tarea difiere cada vez   los hilos se crean como necesariamente automáticamente. Los trabajos se ejecutan en paralelo. Con más que   ese (maxConcurrentOperationCount) es alcanzado, algunas tareas se comportarán   como una serie hasta que un hilo esté 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 sincronización en GCD. Sync ejecuta un bloque en una cola determinada y espera que se complete, lo que resulta en el bloqueo del hilo principal o la cola principal. Dado que la cola principal debe esperar hasta que el bloque enviado finalice, el hilo principal estará disponible para procesar bloques de colas que no sean cola principal. Por lo tanto, existe la posibilidad de que el código que se está ejecutando en la cola de fondo realmente se esté ejecutando en el hilo principal.         Debido a que su cola es concurrente, las tareas pueden no finalizar en el orden en que se agregan a la cola. Pero con la operación sincrónica sí lo hace, aunque pueden ser procesados ​​por diferentes hilos. Entonces, se comporta como esta es la cola serial.

Aquí hay un resumen de estos experimentos

Recuerde usar GCD, solo está agregando tareas a la cola y realizando tareas desde esa cola. Cola envía su tarea en el hilo principal o en el fondo dependiendo de si la operación es síncrona o asíncrona. Los tipos de colas son en serie, concurrentes, 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 su aplicación y una cola principal (DispatchQueue.main). Usted también puede crear manualmente su propia cola y realizar tareas desde esa cola.

La tarea relacionada con la interfaz de usuario debe realizarse siempre desde el hilo principal enviando la tarea a la cola principal. La utilidad de la mano corta es DispatchQueue.main.sync/async mientras que las operaciones pesadas / relacionadas con la red siempre deben realizarse de forma asincrónica, sin importar si alguna vez se está utilizando el hilo principal o el fondo

EDITAR: Sin embargo, hay casos en los que necesita realizar operaciones de llamadas de red de forma síncrona en una cadena de fondo sin congelar la IU (por ejemplo, restaurar el Token OAuth y esperar si tiene éxito o no). Debe ajustar ese método dentro de una operación asíncrona. De esta manera, sus operaciones pesadas 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 Puedes ver un video de demostración aquí


82
2018-04-04 09:25



Si entiendo correctamente cómo funciona GCD, creo que hay dos tipos de DispatchQueue, serial y concurrent, al mismo tiempo, hay dos formas de cómo DispatchQueue despachar sus tareas, el asignado closure, el primero es asyncy el otro es sync. Aquellos juntos determinan cómo se ejecuta realmente el cierre (tarea).

encontre eso serial y concurrentsignifica cuántos hilos puede usar la cola, serial significa uno, mientras que concurrent significa muchos. Y sync y async significa que la tarea se ejecutará en qué subproceso, el subproceso de la persona que llama o el subproceso subyacente a esa cola, sync significa correr en el hilo de la persona que llama mientras async significa correr 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 útil.


4