Multithreading iOS : file d'attente série GCD, file d'attente simultanée, exécution synchrone et exécution asynchrone
Files d'attente dans GCD
En raison du niveau limité, le contenu suivant n’est pas garanti comme étant correct. Veuillez lire le contenu suivant avec un œil critique. Si vous trouvez des erreurs, veuillez les corriger.
1 Qu’est-ce que la file d’attente ?
Avant de démarrer GCD, parlons du concept de file d’attente, car les tâches GCD sont toutes réparties dans la file d’attente ;
File d’attente : Il s’agit d’un tableau linéaire du premier entré, premier sorti (FIFO, First-In-First-Out). Cependant, après avoir ajouté les deux attributs 串行 et 并发 devant 队列, c’est-à-dire 串行队列 et 并发队列, il est parfois facile de se confondre, surtout après avoir ajouté les concepts de 同步 et 异步, parfois cela devient encore plus flou.
2 File d’attente série et file d’attente simultanée
Notez qu’il s’agit de 并发队列(Concurrent Queue) et non de 并行队列. Voir la section suivante pour connaître la différence entre 并发 et 并行.
Que sont les 串行队列 et 并发队列 ? Comme mentionné ci-dessus, 串行 et 并发 dans 串行队列 et 并发队列 sont des attributs de 队列. Vous pouvez ajouter 的, ZXPH 00031ZX et 并发的队列 ; donc 串行队列 et 并行队列 sont toujours 队列 en dernière analyse. Puisqu’il s’agit de 队列, il doit être premier entré, premier sorti (FIFO, First-In-First-Out), il est important de s’en souvenir.
串行队列 : Cela signifie que les tâches de cette file d’attente doivent être exécutées par 串行, c’est-à-dire qu’elles sont exécutées une par une. Ils doivent attendre la fin de la tâche précédente avant de commencer la suivante, et ils doivent être exécutés dans l’ordre du premier entré, premier sorti. Par exemple, il y a 4 tâches dans 串行队列, et l’ordre d’entrée dans la file d’attente est a, b, c, d, puis a doit être exécuté en premier, et une fois la tâche a terminée, b…
并发队列 : Cela signifie que les tâches de cette file d’attente peuvent être exécutées par 并发, c’est-à-dire que les tâches peuvent être exécutées en même temps. Par exemple, il y a 4 tâches dans 并发队列. L’ordre d’entrée dans la file d’attente est a, b, c, d. Alors a doit être exécuté en premier, puis b…, mais a peut ne pas être terminé lorsque b est exécuté, et on ne sait pas lequel de a et b est exécuté en premier. Le nombre d’exécutions simultanées est contrôlé par le système (le nombre de concurrence ne peut pas être défini directement dans GCD, il peut être obtenu en créant un sémaphore, NSOperationQueue peut être défini directement), mais il doit également être appelé selon le principe du premier entré, premier sorti (FIFO, First-In-First-Out).
4 À propos de 并发 et 并行
Le mot anglais pour parallélisme est parallélisme, et le mot anglais pour concurrence est concurrence.
-
La concurrence signifie la simultanéité des concepts logiques, et le parallèle signifie la simultanéité des concepts physiques.
-
La concurrence fait référence à la nature du code et le parallélisme fait référence à l’état physique d’exécution.
-
La concurrence signifie que l’heure de début du processus B est comprise entre l’heure de début et l’heure de fin du processus A. On dit que A et B sont concurrents. Le parallélisme fait référence à deux threads exécutés simultanément sur des processeurs différents.
-
La concurrence traite beaucoup de choses à la fois, et le parallélisme fait beaucoup de choses à la fois ;
-
La concurrence peut être considérée comme un modèle de conception de structure logique. Vous pouvez utiliser une méthode de conception concurrente pour écrire des programmes, puis les exécuter sur un processeur monocœur, créant ainsi l’illusion de parallélisme grâce à une commutation logique dynamique du processeur. À ce stade, votre programme n’est pas parallèle, mais concurrent. Si vous exécutez un programme simultané sur un processeur multicœur, votre programme peut être considéré comme parallèle à ce stade. Le parallélisme concerne davantage l’exécution du programme (exécution) ;
-
Pour un processeur monocœur, au moins deux processeurs sont nécessaires en parallèle ; mais un processeur peut être utilisé en parallèle et les deux tâches peuvent être exécutées alternativement ;
Pour résumer : la concurrence est davantage un concept de programmation, tandis que le parallélisme est un concept d’exécution physique du processeur. La concurrence peut être réalisée en parallèle. La concurrence est expliquée du point de vue de la programmation et le parallélisme est vu du point de vue des tâches d’exécution du processeur. D’une manière générale, nous ne pouvons écrire que des programmes concurrents, mais rien ne garantit que nous puissions écrire des programmes parallèles.
La concurrence et le parallélisme peuvent être considérés comme des dimensions différentes. La concurrence est vue du point de vue d’un programmeur qui écrit un programme. Le parallélisme est vu à partir de l’exécution physique du programme.
Joe Armstrong, l’inventeur d’Erlang, a mentionné dans l’un de ses articles de blog (http://joearms.github.io/2013/04/05/concurrent-and-parallel-programming.html) comment présenter la différence entre concurrence et parallélisme à un enfant de 5 ans.

Synchrone et asynchrone
同步 et 异步 dans GCD sont destinés à l’exécution de tâches, c’est-à-dire à l’exécution synchrone de tâches et à l’exécution asynchrone de tâches. Synchrone ou asynchrone décrit la relation entre une tâche et son contexte
Exécution synchrone : On comprend que lorsqu’une fonction est appelée (ou lorsqu’un bloc de code est exécuté), le code suivant doit être exécuté une fois la fonction (ou le bloc de code) terminée. L’exécution synchrone exécute généralement les tâches dans le thread actuel et ne démarre pas de nouveau thread.
Asynchrone : que la fonction appelée ait été exécutée ou non, le code suivant continuera à être exécuté. Avoir la possibilité de démarrer de nouveaux fils de discussion.
La principale différence entre synchrone et asynchrone est de savoir s’il faut revenir immédiatement lors de l’ajout d’une tâche à la file d’attente ou attendre que la tâche ajoutée soit terminée avant de revenir.
dispatch_sync(dispatch_queue_t queue, dispatch_block_t block);
dispatch_async(dispatch_queue_t queue, dispatch_block_t block);
dispatch_sync est utilisé pour ajouter des tâches de synchronisation. Lors de l’ajout de tâches, vous devez attendre que le code du bloc soit exécuté avant que la fonction dispatch_sync puisse revenir.
dispatch_async ajoute des tâches asynchrones. Lors de l’ajout d’une tâche, elle sera renvoyée immédiatement, que le code du bloc soit exécuté ou non.
###Tester
- Tâches asynchrones de file d’attente série Le code suivant est exécuté dans la méthode 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)}
Vous pouvez voir que la sortie est séquentielle, dans le même thread, et qu’un nouveau thread est ouvert.
- Tâche de synchronisation de la file d’attente série
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}
Vous pouvez voir que la sortie est séquentielle, dans le même thread, mais aucun nouveau thread n’est démarré et il est exécuté dans le thread principal.
- Tâches asynchrones de file d’attente simultanée
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)}
Comme vous pouvez le voir, il est exécuté simultanément et plusieurs nouveaux threads sont ouverts.
Avez-vous trouvé quelque chose qui ne va pas ici ? Il est compréhensible que l’ordre dans lequel l’exécution est terminée soit incertain, mais pourquoi l’ordre dans lequel elle est commencée est-il également incertain ? Selon ce qui précède, la file d’attente est premier entré, premier sorti, donc 我开始了 doit être imprimé dans l’ordre, mais l’impression réelle est dans le désordre. Pourquoi? Ce problème n’a pas encore été clarifié. Je suppose que cela peut être dû au fonctionnement fastidieux de NSLog (@“J’ai démarré : %@, %@”,@(i),[NSThread currentThread]);.
- Tâches simultanées de synchronisation des files d’attente
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}
On peut voir que le programme n’est pas exécuté simultanément et qu’aucun nouveau thread n’est démarré. Il est exécuté sur le thread principal. Vous trouvez ça étrange ? Pourquoi les tâches ajoutées à la file d’attente simultanée ne démarrent-elles pas un nouveau thread mais sont exécutées sur le thread principal ? Expliqué ci-dessous :
使用dispatch_sync 添加同步任务,必须等添加的block执行完成之后才返回。
既然要执行block,肯定需要线程,要么新开线程执行,要么再已存在的线程(包括当前线程)执行。
dispatch_sync的官方注释里面有这么一句话:
As an optimization, dispatch_sync() invokes the block on the current thread when possible.
作为优化,如果可能,直接在当前线程调用这个block。
所以,一般,在大多数情况下,通过dispatch_sync添加的任务,在哪个线程添加就会在哪个线程执行。
上面我们添加的任务的代码是在主线程,所以就直接在主线程执行了。
Toutes les tâches de la file d’attente série sont-elles exécutées sur un seul thread ?
Le test est le suivant
- (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)}
On peut voir que les tâches de synchronisation ajoutées à la file d’attente série sont exécutées sur le thread principal, ce qui est cohérent avec la conclusion ci-dessus (les tâches ajoutées via dispatch_sync seront exécutées dans quel thread elles sont ajoutées). La tâche asynchrone est exécutée dans un thread nouvellement ouvert et un seul thread est ouvert.
Refaites le test suivant :
- (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)}
Vous pouvez voir :
-
Les tâches de synchronisation ajoutées à la file d’attente série personnalisée dans le thread principal sont exécutées directement dans le thread principal
-
Les tâches asynchrones ajoutées à la file d’attente série personnalisée dans le thread principal ouvriront un nouveau thread
-
Les tâches de synchronisation ajoutées à la file d’attente série personnalisée dans les threads non principaux sont exécutées directement dans le thread actuel
-
Les tâches asynchrones ajoutées à une file d’attente série personnalisée dans un thread non principal sont exécutées directement dans le thread actuel
Conclusion : les tâches ajoutées à la file d’attente de répartition série à l’aide de la fonction dispatch_sync s’exécutent souvent sur le même thread que le contexte dans lequel elles s’exécutent ; les tâches ajoutées à la file d’attente de répartition série à l’aide de la fonction dispatch_async ouvriront généralement (pas nécessairement) un nouveau thread, mais différentes tâches asynchrones utilisent le même thread.
####Tester :
-
Le thread principal exécutera-t-il uniquement les tâches de la file d’attente principale ? Non, comme ci-dessus
-
Quel est le résultat de l’exécution du code suivant ? Pourquoi?
- (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} est émis, puis un blocage se produit.
Raison : l’utilisation de dispatch_sync pour ajouter des tâches à la file d’attente série sera exécutée dans le thread actuel, et le thread actuel est le thread principal, donc la première sortie NSLog, car le code de bloc du premier dispatch_sync est exécuté dans le thread principal, donc le deuxième dispatch_sync est équivalent à l’écriture suivante, donc un blocage se produira. Si vous ne comprenez pas pourquoi, recherchez Google.
- (void)viewDidLoad {
dispatch_sync(dispatch_get_main_queue(), ^{
NSLog(@"current thread = %@", [NSThread currentThread]);
});
}
Une impasse se produira,
Question
- (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)}
Pourquoi ne pas commencer dans l’ordre ? Les files d’attente simultanées sont également des files d’attente. La file d’attente doit être la première entrée, première sortie. Bien que l’ordre de fin d’exécution soit incertain, il convient de le déterminer dès le début.
- (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)}
Après avoir ajouté NSLog (@“”);, il démarre dans l’ordre. Pourquoi? Si vous le savez, n’hésitez pas à m’apprendre.
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