Multithreading iOS - fila serial GCD, fila simultânea, execução síncrona e execução assíncrona
Filas no GCD
Devido ao nível limitado, não há garantia de que o conteúdo a seguir esteja correto. Por favor, leia o conteúdo a seguir com um olhar crítico. Se você encontrar algum erro, corrija-o.
1 O que é fila?
Antes de iniciar o GCD, vamos falar sobre o conceito de fila, pois as tarefas do GCD são todas despachadas na fila;
Fila: É uma tabela linear de primeiro a entrar, primeiro a sair (FIFO, First-In-First-Out). Porém, após adicionar os dois atributos 串行 e 并发 na frente de 队列, ou seja, 串行队列 e 并发队列, às vezes é fácil se confundir, principalmente depois de adicionar os conceitos de 同步 e 异步, às vezes fica ainda mais confuso.
2 Fila serial e fila simultânea
Observe que é 并发队列(Concurrent Queue), não 并行队列. Consulte a próxima seção para saber a diferença entre 并发 e 并行
O que são 串行队列 e 并发队列? Conforme mencionado acima, 串行 e 并发 em 串行队列 e 并发队列 são atributos de 队列. Você pode adicionar 的, ZXPH 00031ZX e 并发的队列; então 串行队列 e 并行队列 ainda são 队列 na análise final. Por ser 队列, deve ser o primeiro a entrar, primeiro a sair (FIFO, First-In-First-Out), é importante lembrar disso.
串行队列: Significa que as tarefas desta fila precisam ser executadas por 串行, ou seja, são executadas uma a uma. Eles devem aguardar a conclusão da tarefa anterior antes de iniciar a próxima e devem ser executados na ordem de primeiro a entrar, primeiro a sair. Por exemplo, existem 4 tarefas em 串行队列, e a ordem de entrada na fila é a, b, c, d, então a deve ser executada primeiro, e após a conclusão da tarefa a, b…
并发队列: Significa que as tarefas desta fila podem ser executadas por 并发, ou seja, as tarefas podem ser executadas ao mesmo tempo. Por exemplo, existem 4 tarefas em 并发队列. A ordem de entrada na fila é a, b, c, d. Então a deve ser executado primeiro, depois b…, mas a pode não ser concluído quando b é executado, e é incerto qual de a e b é executado primeiro. Quantos são executados ao mesmo tempo é controlado pelo sistema (o número de simultaneidade não pode ser definido diretamente no GCD, pode ser conseguido criando um semáforo, NSOperationQueue pode ser definido diretamente), mas também deve ser chamado de acordo com o princípio do primeiro a entrar, primeiro a sair (FIFO, First-In-First-Out).
4 Sobre 并发 e 并行
A palavra inglesa para paralelismo é paralelismo, e a palavra inglesa para simultaneidade é simultaneidade.
-
Simultaneidade significa simultaneidade em conceitos lógicos, e paralelo significa simultaneidade em conceitos físicos.
-
A simultaneidade refere-se à natureza do código e o paralelismo refere-se ao estado físico de execução.
-
Simultaneidade significa que o horário de início do processo B está entre o horário de início e o horário de término do processo A. Dizemos que A e B são concorrentes. Paralelismo refere-se a dois threads executados em CPUs diferentes ao mesmo tempo.
-
A simultaneidade é lidar com muitas coisas ao mesmo tempo, e o paralelismo é fazer muitas coisas ao mesmo tempo;
-
A simultaneidade pode ser considerada um padrão de design de estrutura lógica. Você pode usar um método de design simultâneo para escrever programas e, em seguida, executá-los em uma CPU de núcleo único, criando a ilusão de paralelismo por meio da comutação lógica dinâmica da CPU. Neste ponto, seu programa não é paralelo, mas sim concorrente. Se você executar um programa simultâneo em uma CPU multi-core, seu programa poderá ser considerado paralelo neste momento. O paralelismo está mais preocupado com a execução (execução) do programa;
-
Para uma CPU de núcleo único, são necessárias pelo menos duas CPUs em paralelo; mas uma CPU pode ser usada em paralelo e as duas tarefas podem ser executadas alternadamente;
Resumindo: a simultaneidade é mais um conceito de programação, enquanto o paralelismo é um conceito de execução física da CPU. A simultaneidade pode ser alcançada em paralelo. A simultaneidade é explicada do ponto de vista da programação e o paralelismo é visto do ponto de vista das tarefas de execução da CPU. De modo geral, só podemos escrever programas simultâneos, mas não há garantia de que possamos escrever programas paralelos.
A simultaneidade e o paralelismo podem ser considerados dimensões diferentes. A simultaneidade é vista da perspectiva de um programador que escreve um programa. O paralelismo é visto a partir da execução física do programa.
Joe Armstrong, o inventor de Erlang, mencionou em uma de suas postagens no blog (http://joearms.github.io/2013/04/05/concurrent-and-parallel-programming.html) como apresentar a diferença entre simultaneidade e paralelismo para uma criança de 5 anos

Síncrono e assíncrono
同步 e 异步 no GCD são para execução de tarefas, ou seja, execução síncrona de tarefas e execução assíncrona de tarefas. Síncrono ou assíncrono descreve a relação entre uma tarefa e seu contexto
Execução síncrona: Pode-se entender que quando uma função é chamada (ou quando um bloco de código é executado), o código seguinte deve ser executado após a conclusão da função (ou bloco de código). A execução síncrona geralmente executa tarefas no thread atual e não inicia um novo thread.
Assíncrono: independentemente de a função chamada ter sido executada ou não, o código a seguir continuará a ser executado. Tenha a capacidade de iniciar novos tópicos.
A principal diferença entre síncrono e assíncrono é retornar imediatamente ao adicionar uma tarefa à fila ou aguardar até que a tarefa adicionada seja concluída antes de retornar.
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
dispatch_sync é usado para adicionar tarefas de sincronização. Ao adicionar tarefas, você deve esperar até que o código no bloco seja executado antes que a função dispatch_sync possa retornar.
dispatch_async adiciona tarefas assíncronas. Ao adicionar uma tarefa, ela retornará imediatamente, independentemente de o código do bloco ser executado.
Teste
- Tarefas assíncronas de fila serial O código a seguir é executado no 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)}
Você pode ver que a saída está em sequência, no mesmo thread, e um novo thread é aberto.
- Tarefa de sincronização de fila serial
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}
Você pode ver que a saída está em sequência, no mesmo thread, mas nenhum novo thread é iniciado e é executado no thread principal.
- Tarefas assíncronas de fila simultânea
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 você pode ver, ele é executado simultaneamente e mais de um novo thread é aberto.
Você encontrou algo errado aqui? É compreensível que a ordem em que a execução é concluída seja incerta, mas por que a ordem em que ela é iniciada também é incerta? De acordo com o exposto acima, a fila é a primeira a entrar, a primeira a sair, então 我开始了 deve ser impresso em ordem, mas a impressão real está fora de ordem. Por que? Este problema ainda não foi esclarecido. Meu palpite é que isso pode ser causado pela operação demorada de NSLog(@“Eu comecei: %@, %@”,@(i),[NSThread currentThread]);.
- Tarefas simultâneas de sincronização de filas
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}
Pode-se observar que o programa não é executado simultaneamente e nenhum novo thread é iniciado. É executado no thread principal. Você acha isso estranho? Por que as tarefas adicionadas à fila simultânea não iniciam um novo thread, mas são executadas no thread principal? Explicado abaixo:
使用dispatch_sync 添加同步任务,必须等添加的block执行完成之后才返回。
既然要执行block,肯定需要线程,要么新开线程执行,要么再已存在的线程(包括当前线程)执行。
dispatch_sync的官方注释里面有这么一句话:
As an optimization, dispatch_sync() invokes the block on the current thread when possible.
作为优化,如果可能,直接在当前线程调用这个block。
所以,一般,在大多数情况下,通过dispatch_sync添加的任务,在哪个线程添加就会在哪个线程执行。
上面我们添加的任务的代码是在主线程,所以就直接在主线程执行了。
Todas as tarefas na fila serial são executadas em um thread?
O teste é o seguinte
- (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)}
Pode-se observar que as tarefas de sincronização adicionadas à fila serial são executadas no thread principal, o que é consistente com a conclusão acima (as tarefas adicionadas por meio de dispatch_sync serão executadas no thread em que forem adicionadas). A tarefa assíncrona é executada em um thread recém-aberto e apenas um thread é aberto.
Faça o seguinte teste novamente:
- (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)}
Você pode ver:
-
As tarefas de sincronização adicionadas à fila serial personalizada no thread principal são executadas diretamente no thread principal
-
Tarefas assíncronas adicionadas à fila serial personalizada no thread principal abrirão um novo thread
-
As tarefas de sincronização adicionadas à fila serial personalizada em threads não principais são executadas diretamente no thread atual
-
Tarefas assíncronas adicionadas a uma fila serial personalizada em um thread não principal são executadas diretamente no thread atual
Conclusão: As tarefas adicionadas à fila de despacho serial usando a função dispatch_sync geralmente são executadas no mesmo encadeamento do contexto em que estão sendo executadas; tarefas adicionadas à fila de despacho serial usando a função dispatch_async geralmente (não necessariamente) abrem um novo thread, mas diferentes tarefas assíncronas usam o mesmo thread.
Teste:
-
O thread principal executará apenas tarefas na fila principal? Não, como acima
-
Qual é o resultado da execução do código a seguir? Por que?
- (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]);
});
});
}
current thread = <NSThread: 0x60000006d600>{number = 1, name = main} é gerado e, em seguida, ocorre um conflito.
Motivo: usar dispatch_sync para adicionar tarefas à fila serial será executado no thread atual, e o thread atual é o thread principal, portanto, a primeira saída NSLog, porque o código de bloco do primeiro dispatch_sync é executado no thread principal, então o segundo dispatch_sync é equivalente à escrita a seguir, portanto, ocorrerá um deadlock. Se você não entende o porquê, pesquise no Google.
- (void)viewDidLoad {
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"current thread = %@", [NSThread currentThread]);
});
}
Ocorrerá um impasse,
Pergunta
- (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 que não começar em ordem? Filas simultâneas também são filas. A fila deve ser o primeiro a entrar, primeiro a sair. Embora a ordem do fim da execução seja incerta, ela deve ser determinada no início.
- (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)}
Após adicionar NSLog(@“”);, ele inicia em sequência. Por que? Se você sabe, fique à vontade para me ensinar.
What to read next
Want more posts about iOS?
Posts in the same category are usually the best next step for reading more on this topic.
View same categoryWant to keep following #iOS?
Tags are useful for related tools, specific problems, and similar troubleshooting notes.
View same tagWant to explore another direction?
If you are not sure what to read next, return to the homepage and start from categories, topics, or latest updates.
Back home