Back home

Subprocesos múltiples de iOS: cola en serie GCD, cola concurrente, ejecución sincrónica y ejecución asincrónica

Colas en GCD

Debido al nivel limitado, no se garantiza que el siguiente contenido sea correcto. Lea el siguiente contenido con ojo crítico. Si encuentra algún error, corríjalo.

1 ¿Qué es la cola?

Antes de comenzar con GCD, hablemos del concepto de cola, porque todas las tareas de GCD se envían en la cola; Cola: Es una tabla lineal de primero en entrar, primero en salir (FIFO, primero en entrar, primero en salir). Sin embargo, después de agregar los dos atributos 串行 y 并发 delante de 队列, es decir, 串行队列 y 并发队列, a veces es fácil confundirse, especialmente después de agregar los conceptos de 同步 y 异步, a veces se vuelve aún más confuso.

2 Cola en serie y cola concurrente

Tenga en cuenta que es 并发队列(Concurrent Queue), no 并行队列. Consulte la siguiente sección para conocer la diferencia entre 并发 y 并行.

¿Qué son 串行队列 y 并发队列? Como se mencionó anteriormente, 串行 y 并发 en 串行队列 y 并发队列 son atributos de 队列. Puede agregar , ZXPH 00031ZX y 并发的队列; entonces 串行队列 y 并行队列 siguen siendo 队列 en el análisis final. Dado que es 队列, debe ser el primero en entrar, primero en salir (FIFO, primero en entrar, primero en salir), es importante recordar esto.

串行队列: Significa que las tareas en esta cola deben ser ejecutadas por 串行, es decir, se ejecutan una por una. Deben esperar a que se complete la tarea anterior antes de comenzar la siguiente, y deben ejecutarse en el orden de primero en entrar, primero en salir. Por ejemplo, hay 4 tareas en 串行队列, y el orden de ingreso a la cola es a, b, c, d, luego a debe ejecutarse primero y después de completar la tarea a, b…

并发队列: Significa que las tareas en esta cola pueden ser ejecutadas por 并发, es decir, las tareas se pueden ejecutar al mismo tiempo. Por ejemplo, hay 4 tareas en 并发队列. El orden de entrada a la cola es a, b, c, d. Entonces a debe ejecutarse primero, luego b…, pero es posible que a no se complete cuando se ejecuta b, y no se sabe cuál de a y b se ejecuta primero. El sistema controla cuántos se ejecutan al mismo tiempo (el número de concurrencia no se puede establecer directamente en GCD, se puede lograr creando un semáforo, NSOperationQueue se puede configurar directamente), pero también debe llamarse de acuerdo con el principio de primero en entrar, primero en salir (FIFO, primero en entrar, primero en salir).

4 Acerca de 并发 y 并行

La palabra inglesa para paralelismo es paralelismo y la palabra inglesa para concurrencia es concurrencia.

  1. Concurrencia significa simultaneidad en conceptos lógicos y paralelo significa simultaneidad en conceptos físicos.

  2. La concurrencia se refiere a la naturaleza del código y el paralelismo se refiere al estado físico de ejecución.

  3. Concurrencia significa que la hora de inicio del proceso B está entre la hora de inicio y la hora de finalización del proceso A. Decimos que A y B son concurrentes. El paralelismo se refiere a dos subprocesos que se ejecutan en diferentes CPU al mismo tiempo.

  4. La concurrencia consiste en abordar muchas cosas a la vez y el paralelismo consiste en hacer muchas cosas a la vez;

  5. La concurrencia puede considerarse como un patrón de diseño de estructura lógica. Puede utilizar un método de diseño concurrente para escribir programas y luego ejecutarlos en una CPU de un solo núcleo, creando la ilusión de paralelismo mediante la conmutación lógica dinámica de la CPU. En este punto, su programa no es paralelo, sino concurrente. Si ejecuta un programa concurrente en una CPU de múltiples núcleos, su programa puede considerarse paralelo en este momento. El paralelismo está más relacionado con la ejecución del programa (ejecución);

  6. Para una CPU de un solo núcleo, se necesitan al menos dos CPU en paralelo; pero se puede utilizar una CPU en paralelo y las dos tareas se pueden ejecutar alternativamente;

En resumen: la concurrencia es más un concepto en programación, mientras que el paralelismo es un concepto en la ejecución física de la CPU. La concurrencia se puede lograr en paralelo. La concurrencia se explica desde la perspectiva de la programación y el paralelismo se ve desde la perspectiva de las tareas de ejecución de la CPU. En términos generales, solo podemos escribir programas concurrentes, pero no hay garantía de que podamos escribir programas paralelos.

La concurrencia y el paralelismo pueden considerarse dimensiones diferentes. La concurrencia se ve desde la perspectiva de un programador que escribe un programa. El paralelismo se ve desde la ejecución física del programa.

Joe Armstrong, el inventor de Erlang, mencionó en una de las publicaciones de su blog (http://joearms.github.io/2013/04/05/concurrent-and-parallel-programming.html) cómo presentar la diferencia entre concurrencia y paralelismo a un niño de 5 años.

Sincrónico y asincrónico

同步 y 异步 en GCD son para ejecución de tareas, es decir, ejecución sincrónica de tareas y ejecución asincrónica de tareas. Sincrónico o asincrónico describe la relación entre una tarea y su contexto.

Ejecución sincrónica: se puede entender que cuando se llama a una función (o cuando se ejecuta un bloque de código), el siguiente código debe ejecutarse después de que se completa la función (o bloque de código). La ejecución sincrónica generalmente ejecuta tareas en el hilo actual y no inicia un nuevo hilo.

Asíncrono: Independientemente de si la función llamada se ha ejecutado o no, el siguiente código seguirá ejecutándose. Tener la capacidad de iniciar nuevos hilos.

La principal diferencia entre síncrono y asíncrono es si regresar inmediatamente al agregar una tarea a la cola o esperar hasta que se complete la tarea agregada antes de regresar.

dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);

despacho_sync se utiliza para agregar tareas de sincronización. Al agregar tareas, debe esperar hasta que se ejecute el código del bloque antes de que la función despacho_sync pueda regresar.

despacho_async agrega tareas asincrónicas. Al agregar una tarea, regresará inmediatamente, independientemente de si se ejecuta el código en el bloque.

Prueba

  1. Tareas asincrónicas en cola en serie El siguiente código se ejecuta en el método viewDidLoad
dispatch_queue_t serialQueue = dispatch_queue_create("Dan-serial", DISPATCH_QUEUE_SERIAL);
    for(int i = 0; i < 5; i++){
        dispatch_async(serialQueue, ^{
            NSLog(@"我开始了:%@ , %@",@(i),[NSThread currentThread]);
            [NSThread sleepForTimeInterval: i % 3];
        });
    }
输出如下:    
我开始了:0 , <NSThread: 0x60400027e480>{number = 3, name = (null)}
我开始了:1 , <NSThread: 0x60400027e480>{number = 3, name = (null)}
我开始了:2 , <NSThread: 0x60400027e480>{number = 3, name = (null)}
我开始了:3 , <NSThread: 0x60400027e480>{number = 3, name = (null)}
我开始了:4 , <NSThread: 0x60400027e480>{number = 3, name = (null)}

Puede ver que la salida está en secuencia, en el mismo hilo y se abre un nuevo hilo.

  1. Tarea de sincronización de cola en serie
for(int i = 0; i < 5; i++){
        dispatch_sync(serialQueue, ^{
            NSLog(@"我开始了:%@ , %@",@(i),[NSThread currentThread]);
            [NSThread sleepForTimeInterval: i % 3];
        });
    }  
输出如下:    
我开始了:0 , <NSThread: 0x60000006d8c0>{number = 1, name = main}
我开始了:1 , <NSThread: 0x60000006d8c0>{number = 1, name = main}
我开始了:2 , <NSThread: 0x60000006d8c0>{number = 1, name = main}
我开始了:3 , <NSThread: 0x60000006d8c0>{number = 1, name = main}
我开始了:4 , <NSThread: 0x60000006d8c0>{number = 1, name = main}

Puede ver que la salida es secuencial, en el mismo hilo, pero no se inicia ningún hilo nuevo y se ejecuta en el hilo principal.

  1. Tareas asincrónicas en cola concurrentes
    dispatch_queue_t concurrent_queue = dispatch_queue_create("DanCONCURRENT", DISPATCH_QUEUE_CONCURRENT);
    for(int i = 0; i < 5; i++){
        dispatch_async(concurrent_queue, ^{
            NSLog(@"我开始了:%@ , %@",@(i),[NSThread currentThread]);
            [NSThread sleepForTimeInterval: i % 3];
            NSLog(@"执行完成:%@ , %@",@(i),[NSThread currentThread]);
        });
    }
输出如下:
我开始了:0 , <NSThread: 0x600000462340>{number = 3, name = (null)}
我开始了:2 , <NSThread: 0x604000269380>{number = 6, name = (null)}
我开始了:3 , <NSThread: 0x604000269180>{number = 5, name = (null)}
我开始了:1 , <NSThread: 0x600000461d80>{number = 4, name = (null)}
执行完成:0 , <NSThread: 0x600000462340>{number = 3, name = (null)}
执行完成:3 , <NSThread: 0x604000269180>{number = 5, name = (null)}
我开始了:4 , <NSThread: 0x600000462340>{number = 3, name = (null)}
执行完成:1 , <NSThread: 0x600000461d80>{number = 4, name = (null)}
执行完成:4 , <NSThread: 0x600000462340>{number = 3, name = (null)}
执行完成:2 , <NSThread: 0x604000269380>{number = 6, name = (null)}

Como puede ver, se ejecuta al mismo tiempo y se abre más de un hilo nuevo. ¿Encontraste algo malo aquí? Es comprensible que el orden en que se completa la ejecución sea incierto, pero ¿por qué también lo es el orden en que se inicia? De acuerdo con lo anterior, la cola es el primero en entrar, primero en salir, por lo que 我开始了 debe imprimirse en orden, pero la impresión real no funciona. ¿Por qué? Este problema aún no se ha aclarado. Supongo que puede deberse a la operación que consume mucho tiempo de NSLog(@“Empecé: %@, %@”,@(i),[NSThread currentThread]);.

  1. Tareas simultáneas de sincronización de colas
 for(int i = 0; i < 5; i++){
        dispatch_sync(concurrent_queue, ^{
            NSLog(@"我开始了:%@ , %@",@(i),[NSThread currentThread]);
            [NSThread sleepForTimeInterval: i % 3];
            NSLog(@"执行完成:%@ , %@",@(i),[NSThread currentThread]);
        });
    }
    
输出如下:
我开始了:0 , <NSThread: 0x60000007ec80>{number = 1, name = main}
执行完成:0 , <NSThread: 0x60000007ec80>{number = 1, name = main}
我开始了:1 , <NSThread: 0x60000007ec80>{number = 1, name = main}
执行完成:1 , <NSThread: 0x60000007ec80>{number = 1, name = main}
我开始了:2 , <NSThread: 0x60000007ec80>{number = 1, name = main}
执行完成:2 , <NSThread: 0x60000007ec80>{number = 1, name = main}
我开始了:3 , <NSThread: 0x60000007ec80>{number = 1, name = main}
执行完成:3 , <NSThread: 0x60000007ec80>{number = 1, name = main}
我开始了:4 , <NSThread: 0x60000007ec80>{number = 1, name = main}
执行完成:4 , <NSThread: 0x60000007ec80>{number = 1, name = main}

Se puede ver que el programa no se ejecuta al mismo tiempo y no se inicia ningún hilo nuevo. Se ejecuta en el hilo principal. ¿Te parece extraño? ¿Por qué las tareas agregadas a la cola concurrente no inician un nuevo hilo sino que se ejecutan en el hilo principal? Explicado a continuación:

使用dispatch_sync 添加同步任务,必须等添加的block执行完成之后才返回。
既然要执行block,肯定需要线程,要么新开线程执行,要么再已存在的线程(包括当前线程)执行。  
dispatch_sync的官方注释里面有这么一句话:
As an optimization, dispatch_sync() invokes the block on the current thread when possible.
作为优化,如果可能,直接在当前线程调用这个block。
  
所以,一般,在大多数情况下,通过dispatch_sync添加的任务,在哪个线程添加就会在哪个线程执行。

上面我们添加的任务的代码是在主线程,所以就直接在主线程执行了。

¿Todas las tareas de la cola serie se ejecutan en un hilo?

La prueba es la siguiente.

- (void)viewDidLoad {
    dispatch_queue_t serialQueue = dispatch_queue_create("Dan-serial", DISPATCH_QUEUE_SERIAL);

  dispatch_sync(serialQueue, ^{
      // block 1
      NSLog(@"current 1: %@", [NSThread currentThread]);
  });

  dispatch_sync(serialQueue, ^{
      // block 2
      NSLog(@"current 2: %@", [NSThread currentThread]);
  });

  dispatch_async(serialQueue, ^{
      // block 3
      NSLog(@"current 3: %@", [NSThread currentThread]);
  });

  dispatch_async(serialQueue, ^{
      // block 4
      NSLog(@"current 4: %@", [NSThread currentThread]);
  });
}
  //结果如下
  //    current 1: <NSThread: 0x600000071600>{number = 1, name = main}
//    current 2: <NSThread: 0x600000071600>{number = 1, name = main}
//    current 3: <NSThread: 0x60400027bcc0>{number = 3, name = (null)}
//    current 4: <NSThread: 0x60400027bcc0>{number = 3, name = (null)}

Se puede ver que las tareas de sincronización agregadas a la cola en serie se ejecutan en el hilo principal, lo cual es consistente con la conclusión anterior (las tareas agregadas a través de despacho_sync se ejecutarán en qué hilo se agregan). La tarea asincrónica se ejecuta en un hilo recién abierto y solo se abre un hilo.

Haga la siguiente prueba nuevamente:

- (void)viewDidLoad {
    dispatch_queue_t queue = dispatch_queue_create("Dan", NULL);
     dispatch_async(queue, ^{
      NSLog(@"current : %@", [NSThread currentThread]);
      dispatch_queue_t serialQueue = dispatch_queue_create("Dan-serial", DISPATCH_QUEUE_SERIAL);

      dispatch_sync(serialQueue, ^{
          // block 1
          NSLog(@"current 1: %@", [NSThread currentThread]);
      });

      dispatch_sync(serialQueue, ^{
          // block 2
          NSLog(@"current 2: %@", [NSThread currentThread]);
      });

      dispatch_async(serialQueue, ^{
          // block 3
          NSLog(@"current 3: %@", [NSThread currentThread]);
      });

      dispatch_async(serialQueue, ^{
          // block 4
          NSLog(@"current 4: %@", [NSThread currentThread]);
      });
  });
}
// 结果如下
//    current  : <NSThread: 0x604000263440>{number = 3, name = (null)}
//    current 1: <NSThread: 0x604000263440>{number = 3, name = (null)}
//    current 2: <NSThread: 0x604000263440>{number = 3, name = (null)}
//    current 3: <NSThread: 0x604000263440>{number = 3, name = (null)}
//    current 4: <NSThread: 0x604000263440>{number = 3, name = (null)}

Puedes ver:

  • Las tareas de sincronización agregadas a la cola en serie personalizada en el hilo principal se ejecutan directamente en el hilo principal

  • Las tareas asincrónicas agregadas a la cola serial personalizada en el hilo principal abrirán un nuevo hilo

  • Las tareas de sincronización agregadas a la cola serial personalizada en subprocesos no principales se ejecutan directamente en el subproceso actual

  • Las tareas asincrónicas agregadas a una cola serial personalizada en un hilo no principal se ejecutan directamente en el hilo actual

Conclusión: las tareas agregadas a la cola de despacho en serie mediante la función despacho_sync a menudo se ejecutan en el mismo hilo que el contexto en el que se ejecutan; Las tareas agregadas a la cola de despacho en serie usando la función Dispat_async generalmente (no necesariamente) abrirán un nuevo hilo, pero diferentes tareas asincrónicas usan el mismo hilo.

Prueba:

  1. ¿El hilo principal solo ejecutará tareas en la cola principal? No, como arriba

  2. ¿Cuál es el resultado de ejecutar el siguiente código? ¿Por qué?

- (void)viewDidLoad {
    [super viewDidLoad];
    dispatch_queue_t queue = dispatch_queue_create("com.dan.queue", DISPATCH_QUEUE_SERIAL);
    dispatch_sync(queue, ^{
        NSLog(@"current thread = %@", [NSThread currentThread]);
        dispatch_sync(dispatch_get_main_queue(), ^{
            NSLog(@"current thread = %@", [NSThread currentThread]);
        });
    });    
}

Se genera current thread = <NSThread: 0x60000006d600>{number = 1, name = main} y luego se produce un punto muerto. Motivo: el uso de despacho_sync para agregar tareas a la cola en serie se ejecutará en el subproceso actual, y el subproceso actual es el subproceso principal, por lo que la primera salida NSLog, debido a que el código de bloque del primer despacho_sync se ejecuta en el subproceso principal, el segundo despacho_sync es equivalente a la siguiente escritura, por lo que se producirá un punto muerto. Si no entiende por qué, busque en Google.

- (void)viewDidLoad {
dispatch_sync(dispatch_get_main_queue(), ^{
    NSLog(@"current thread = %@", [NSThread currentThread]);
        });
}

Se producirá un punto muerto,

Pregunta

- (void)viewDidLoad {
    dispatch_queue_t concurrent_queue = dispatch_queue_create("Dan——CONCURRENT", DISPATCH_QUEUE_CONCURRENT);
    for(int i = 0; i < 10; i++){
        dispatch_async(concurrent_queue, ^{
            NSLog(@"我开始了:%@ , %@",@(i),[NSThread currentThread]);
        });
    }
}
//运行结果如下
//我开始了:3 , <NSThread: 0x60000026e240>{number = 6, name = (null)}
//我开始了:1 , <NSThread: 0x60400027a440>{number = 4, name = (null)}
//我开始了:2 , <NSThread: 0x60000026f800>{number = 5, name = (null)}
//我开始了:0 , <NSThread: 0x60400027a400>{number = 3, name = (null)}
//我开始了:4 , <NSThread: 0x60000026e240>{number = 6, name = (null)}
//我开始了:5 , <NSThread: 0x60400027a440>{number = 4, name = (null)}
//我开始了:8 , <NSThread: 0x60000026e980>{number = 9, name = (null)}
//我开始了:7 , <NSThread: 0x60000026e800>{number = 8, name = (null)}
//我开始了:6 , <NSThread: 0x60000026e8c0>{number = 7, name = (null)}
//我开始了:9 , <NSThread: 0x60400027a280>{number = 10, name = (null)}

¿Por qué no empezar en orden? Las colas concurrentes también son colas. La cola debe ser el primero en entrar, el primero en salir. Aunque el orden del final de la ejecución es incierto, debe determinarse al principio.

- (void)viewDidLoad {
    dispatch_queue_t concurrent_queue = dispatch_queue_create("Dan——CONCURRENT", DISPATCH_QUEUE_CONCURRENT);
    for(int i = 0; i < 10; i++){
        dispatch_async(concurrent_queue, ^{
            NSLog(@"我开始了:%@ , %@",@(i),[NSThread currentThread]);
        });
    }
    NSLog(@"");
}
//运行结果如下
//我开始了:0 , <NSThread: 0x60000027d200>{number = 3, name = (null)}
//我开始了:1 , <NSThread: 0x60000027d740>{number = 4, name = (null)}
//我开始了:2 , <NSThread: 0x60000027d200>{number = 3, name = (null)}
//我开始了:3 , <NSThread: 0x60000027d740>{number = 4, name = (null)}
//我开始了:4 , <NSThread: 0x60000027d200>{number = 3, name = (null)}
//我开始了:5 , <NSThread: 0x60000027d740>{number = 4, name = (null)}
//我开始了:6 , <NSThread: 0x60000027d200>{number = 3, name = (null)}
//我开始了:7 , <NSThread: 0x60000027d740>{number = 4, name = (null)}
//我开始了:8 , <NSThread: 0x60000027d200>{number = 3, name = (null)}
//我开始了:9 , <NSThread: 0x60000027d740>{number = 4, name = (null)}

Después de agregar NSLog(@“”);, comienza en secuencia. ¿Por qué? Si lo sabes, no dudes en enseñármelo.

FAQ

What to read next

Related

Continue reading