J'ai du mal à comprendre pleinement les files d'attente simultanées et série dans GCD. J'ai quelques problèmes et j'espère que quelqu'un pourra me répondre clairement et précisément.
Je lis que des files d'attente en série sont créées et utilisées pour exécuter les tâches les unes après les autres. Cependant, que se passe-t-il si:
- Je crée une file d'attente série
- J'utilise
dispatch_async
(sur la file d'attente série que je viens de créer) trois fois pour envoyer trois blocs A, B, C
Les trois blocs seront-ils exécutés:
dans l'ordre A, B, C car la file d'attente est série
OU
- simultanément (dans le même temps sur les threads parallèles) car j'ai utilisé la distribution ASYNC
Je lis que je peux utiliser
dispatch_sync
sur des files d'attente simultanées afin d'exécuter les blocs les uns après les autres. Dans ce cas, POURQUOI existe-t-il même des files d'attente série, puisque je peux toujours utiliser une file d'attente simultanée où je peux distribuer de manière SYNCHRONE autant de blocs que je veux?Merci pour toute bonne explication!
la source
Réponses:
Un exemple simple: vous avez un bloc qui prend une minute à exécuter. Vous l'ajoutez à une file d'attente à partir du thread principal. Regardons les quatre cas.
De toute évidence, vous n'utiliserez aucun des deux derniers pour les processus de longue durée. Vous le voyez normalement lorsque vous essayez de mettre à jour l'interface utilisateur (toujours sur le thread principal) à partir de quelque chose qui peut être exécuté sur un autre thread.
la source
Voici quelques expériences que je l' ai fait pour me faire comprendre au sujet de ces
serial
, lesconcurrent
files d' attente avecGrand Central Dispatch
.Voici un résumé de ces expériences
N'oubliez pas qu'en utilisant GCD, vous ajoutez uniquement une tâche à la file d'attente et effectuez une tâche à partir de cette file d'attente. La file d'attente distribue votre tâche dans le thread principal ou en arrière-plan selon que l'opération est synchrone ou asynchrone. Les types de files d'attente sont les files d'attente de distribution série, simultanée et principale.Toutes les tâches que vous effectuez sont effectuées par défaut à partir de la file d'attente de distribution principale.Il existe déjà quatre files d'attente simultanées globales prédéfinies pour votre application et une file d'attente principale (DispatchQueue.main). peut également créer manuellement votre propre file d'attente et effectuer une tâche à partir de cette file d'attente.
La tâche liée à l'interface utilisateur doit toujours être effectuée à partir du thread principal en distribuant la tâche à la file d'attente
DispatchQueue.main.sync/async
principale.EDIT: Cependant, dans certains cas, vous devez effectuer des opérations d'appels réseau de manière synchrone dans un thread d'arrière-plan sans geler l'interface utilisateur (par exemple, rafraîchir le jeton OAuth et attendre si cela réussit ou non) .Vous devez envelopper cette méthode dans une opération asynchrone. les opérations sont exécutées dans l'ordre et sans blocage du thread principal.
EDIT EDIT: Vous pouvez regarder la vidéo de démonstration ici
la source
}
car il ne s'exécute vraiment pas à ce momentconcurrentQueue.sync
dedoLongSyncTaskInConcurrentQueue()
fonction, il imprime thread principal,Task will run in different thread
ne semble pas vrai.Tout d'abord, il est important de connaître la différence entre les threads et les files d'attente et ce que fait réellement GCD. Lorsque nous utilisons des files d'attente de répartition (via GCD), nous mettons vraiment en file d'attente, pas en thread. Le framework Dispatch a été conçu spécifiquement pour nous éloigner du threading, car Apple admet que «l'implémentation d'une solution de threading correcte [peut] devenir extrêmement difficile, voire [parfois] impossible à réaliser». Par conséquent, pour effectuer des tâches simultanément (tâches que nous ne voulons pas geler l'interface utilisateur), tout ce que nous devons faire est de créer une file d'attente de ces tâches et de la remettre à GCD. Et GCD gère tous les threads associés. Par conséquent, tout ce que nous faisons vraiment, c'est faire la queue.
La deuxième chose à savoir tout de suite est ce qu'est une tâche. Une tâche est tout le code de ce bloc de file d'attente (pas dans la file d'attente, car nous pouvons ajouter des éléments à une file d'attente tout le temps, mais dans la fermeture où nous l'avons ajouté à la file d'attente). Une tâche est parfois appelée un bloc et un bloc est parfois appelé une tâche (mais ils sont plus communément appelés tâches, en particulier dans la communauté Swift). Et peu importe la quantité ou le peu de code, tout le code entre les accolades est considéré comme une seule tâche:
Et il est évident de mentionner que concourant signifie simplement en même temps avec d'autres choses et signifie série l'un après l'autre (jamais en même temps). Serialiser quelque chose, ou mettre quelque chose en série, signifie simplement l'exécuter du début à la fin dans son ordre de gauche à droite, de haut en bas, sans interruption.
Il existe deux types de files d'attente, série et simultanée, mais toutes les files d'attente sont simultanées l'une par rapport à l'autre . Le fait que vous souhaitiez exécuter n'importe quel code "en arrière-plan" signifie que vous souhaitez l'exécuter simultanément avec un autre thread (généralement le thread principal). Par conséquent, toutes les files d'attente de distribution, en série ou simultanées, exécutent leurs tâches simultanément par rapport aux autres files d'attente . Toute sérialisation effectuée par des files d'attente (par des files d'attente série) n'a à voir qu'avec les tâches de cette seule file d'attente de distribution [série] (comme dans l'exemple ci-dessus où il y a deux tâches dans la même file d'attente série; ces tâches seront exécutées une après l'autre, jamais simultanément).
SERIAL QUEUES (souvent appelées files d'attente de distribution privées) garantissent l'exécution des tâches une par une du début à la fin dans l'ordre dans lequel elles ont été ajoutées à cette file d'attente spécifique. C'est la seule garantie de sérialisation n'importe où dans la discussion des files d'attente d'expédition- que les tâches spécifiques dans une file d'attente série spécifique sont exécutées en série. Cependant, les files d'attente série peuvent s'exécuter simultanément avec d'autres files d'attente série s'il s'agit de files d'attente distinctes car, là encore, toutes les files d'attente sont concurrentes les unes par rapport aux autres. Toutes les tâches s'exécutent sur des threads distincts, mais toutes les tâches ne sont pas garanties de s'exécuter sur le même thread (pas important, mais intéressant à savoir). Et le framework iOS n'est pas livré avec des files d'attente série prêtes à l'emploi, vous devez les créer. Les files d'attente privées (non globales) sont en série par défaut, donc pour créer une file d'attente en série:
Vous pouvez le rendre concurrent grâce à sa propriété d'attribut:
Mais à ce stade, si vous n'ajoutez aucun autre attribut à la file d'attente privée, Apple vous recommande d'utiliser simplement l'une de leurs files d'attente globales prêtes à l'emploi (qui sont toutes simultanées). Au bas de cette réponse, vous verrez une autre façon de créer des files d'attente série (à l'aide de la propriété cible), c'est ainsi qu'Apple recommande de le faire (pour une gestion plus efficace des ressources). Mais pour l'instant, l'étiqueter est suffisant.
Les QUEUES CONCURRENTES (souvent appelées files d'attente de répartition globales) peuvent exécuter des tâches simultanément; les tâches sont toutefois assurées de démarrer dans l'ordre dans lequel elles ont été ajoutées à cette file d'attente spécifique, mais contrairement aux files d'attente série, la file d'attente n'attend pas la fin de la première tâche avant de démarrer la deuxième tâche. Les tâches (comme avec les files d'attente série) s'exécutent sur des threads distincts et (comme avec les files d'attente série) toutes les tâches ne sont pas garanties de s'exécuter sur le même thread (pas important, mais intéressant à savoir). Et le framework iOS est livré avec quatre files d'attente simultanées prêtes à l'emploi. Vous pouvez créer une file d'attente simultanée en utilisant l'exemple ci-dessus ou en utilisant l'une des files d'attente globales d'Apple (ce qui est généralement recommandé):
Il existe deux façons de répartir les files d'attente: de manière synchrone et asynchrone.
SYNC DISPATCHING signifie que le thread où la file d'attente a été distribuée (le thread appelant) s'arrête après avoir distribué la file d'attente et attend que la tâche de ce bloc de file d'attente se termine avant de reprendre. Pour expédier de manière synchrone:
ASYNC DISPATCHING signifie que le thread appelant continue de s'exécuter après la distribution de la file d'attente et n'attend pas que la tâche de ce bloc de file d'attente se termine. Pour distribuer de manière asynchrone:
Maintenant, on pourrait penser que pour exécuter une tâche en série, une file d'attente en série doit être utilisée, et ce n'est pas tout à fait correct. Afin d'exécuter plusieurs tâches en série, une file d'attente en série doit être utilisée, mais toutes les tâches (isolées par elles-mêmes) sont exécutées en série. Prenons cet exemple:
Quelle que soit la façon dont vous configurez (série ou simultanée) ou distribuez (synchronisation ou asynchrone) cette file d'attente, cette tâche sera toujours exécutée en série. La troisième boucle ne s'exécutera jamais avant la deuxième boucle et la deuxième boucle ne s'exécutera jamais avant la première boucle. Cela est vrai dans n'importe quelle file d'attente utilisant n'importe quelle expédition. C'est lorsque vous introduisez plusieurs tâches et / ou files d'attente que la série et la concurrence entrent vraiment en jeu.
Considérez ces deux files d'attente, une série et une simultanée:
Disons que nous distribuons deux files d'attente simultanées en asynchrone:
Leur sortie est confuse (comme prévu) mais notez que chaque file d'attente a exécuté sa propre tâche en série. Il s'agit de l'exemple le plus élémentaire de concurrence d'accès - deux tâches s'exécutant en même temps en arrière-plan dans la même file d'attente. Maintenant, faisons la première série:
La première file d'attente n'est-elle pas censée être exécutée en série? C'était (et c'était le deuxième). Tout ce qui s'est passé en arrière-plan ne concerne pas la file d'attente. Nous avons dit à la file d'attente série de s'exécuter en série et c'est le cas ... mais nous ne lui avons donné qu'une seule tâche. Maintenant, donnons-lui deux tâches:
Et c'est l'exemple le plus basique (et le seul possible) de sérialisation - deux tâches s'exécutant en série (l'une après l'autre) en arrière-plan (vers le thread principal) dans la même file d'attente. Mais si nous leur avons fait deux files d'attente série distinctes (car dans l'exemple ci-dessus, ce sont la même file d'attente), leur sortie est à nouveau brouillée:
Et c'est ce que je voulais dire quand j'ai dit que toutes les files d'attente étaient concurrentes les unes par rapport aux autres. Ce sont deux files d'attente série exécutant leurs tâches en même temps (car ce sont des files d'attente distinctes). Une file d'attente ne connaît pas ou ne se soucie pas des autres files d'attente. Revenons maintenant à deux files d'attente série (de la même file d'attente) et ajoutons une troisième file d'attente, une simultanée:
C'est un peu inattendu, pourquoi la file d'attente simultanée a-t-elle attendu la fin des files d'attente série avant de s'exécuter? Ce n'est pas la concurrence. Votre terrain de jeu peut afficher une sortie différente, mais la mienne l'a montré. Et cela a montré cela parce que la priorité de ma file d'attente simultanée n'était pas assez élevée pour que GCD exécute sa tâche plus tôt. Donc, si je garde tout pareil mais que je change la QoS de la file d'attente globale (sa qualité de service, qui est simplement le niveau de priorité de la file d'attente)
let concurrentQueue = DispatchQueue.global(qos: .userInteractive)
, alors le résultat est comme prévu:Les deux files d'attente série ont exécuté leurs tâches en série (comme prévu) et la file d'attente simultanée a exécuté sa tâche plus rapidement car elle a reçu un niveau de priorité élevé (une QoS élevée ou une qualité de service).
Deux files d'attente simultanées, comme dans notre premier exemple d'impression, montrent une impression confuse (comme prévu). Pour les faire imprimer proprement en série, nous devions créer les deux la même file d'attente série (la même instance de cette file d'attente, pas seulement la même étiquette) . Ensuite, chaque tâche est exécutée en série par rapport à l'autre. Une autre façon, cependant, de les faire imprimer en série est de les garder tous les deux simultanés mais de changer leur méthode d'expédition:
N'oubliez pas que la distribution de synchronisation signifie uniquement que le thread appelant attend que la tâche de la file d'attente soit terminée avant de continuer. La mise en garde ici, évidemment, est que le thread appelant est gelé jusqu'à ce que la première tâche soit terminée, ce qui peut ou non être la façon dont vous souhaitez que l'interface utilisateur fonctionne.
Et c'est pour cette raison que nous ne pouvons pas faire ce qui suit:
Il s'agit de la seule combinaison possible de files d'attente et de méthodes de répartition que nous ne pouvons pas effectuer: répartition synchrone sur la file d'attente principale. Et c'est parce que nous demandons à la file d'attente principale de se figer jusqu'à ce que nous exécutions la tâche dans les accolades ... que nous avons envoyées à la file d'attente principale, que nous venons de geler. C'est ce qu'on appelle une impasse. Pour le voir en action dans une aire de jeux:
Une dernière chose à mentionner concerne les ressources. Lorsque nous attribuons une tâche à une file d'attente, GCD trouve une file d'attente disponible dans son pool géré en interne. En ce qui concerne l'écriture de cette réponse, il y a 64 files d'attente disponibles par qos. Cela peut sembler beaucoup mais ils peuvent être rapidement consommés, en particulier par des bibliothèques tierces, en particulier les frameworks de bases de données. Pour cette raison, Apple a des recommandations sur la gestion des files d'attente (mentionnées dans les liens ci-dessous); un étant:
Pour ce faire, au lieu de les créer comme nous l'avons fait auparavant (ce que vous pouvez toujours), Apple recommande de créer des files d'attente série comme celle-ci:
Pour plus d'informations, je recommande ce qui suit:
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
la source
Si je comprends bien le fonctionnement de GCD, je pense qu'il existe deux types de
DispatchQueue
,serial
etconcurrent
, en même temps, il y a deux manières deDispatchQueue
répartir ses tâches, l'attributionclosure
, la première estasync
, et l'autresync
. Ceux-ci déterminent ensemble la manière dont la fermeture (tâche) est réellement exécutée.J'ai trouvé cela
serial
et jeconcurrent
veux dire combien de threads cette file d'attente peut utiliserserial
signifie un, alors que celaconcurrent
signifie plusieurs. Etsync
etasync
signifie que la tâche sera exécutée sur quel thread, le thread de l'appelant ou le thread sous-jacent à cette file d'attente,sync
signifie s'exécuter sur le thread de l'appelant alors queasync
signifie s'exécuter sur le thread sous-jacent.Ce qui suit est un code expérimental qui peut s'exécuter sur le terrain de jeu Xcode.
J'espère que cela peut être utile.
la source
J'aime penser cela en utilisant cette métaphore (voici le lien vers l'image originale):
Imaginons que votre père fasse la vaisselle et que vous venez de prendre un verre de soda. Vous apportez le verre à votre père pour le nettoyer, en le mettant à côté de l'autre plat.
Maintenant, ton père fait la vaisselle tout seul, alors il va devoir les faire un par un: ton père ici représente une file d'attente en série .
Mais vous n'êtes pas vraiment intéressé à rester là et à le regarder se nettoyer. Alors, vous laissez tomber le verre et retournez dans votre chambre: c'est ce qu'on appelle une répartition asynchrone . Votre père pourrait vous le faire savoir ou non une fois qu'il a terminé, mais le plus important est que vous n'attendez pas que le verre soit nettoyé; tu retournes dans ta chambre pour faire, tu sais, des trucs pour enfants.
Supposons maintenant que vous ayez encore soif et que vous vouliez avoir de l'eau sur ce même verre qui se trouve être votre préféré, et que vous voulez vraiment le récupérer dès qu'il est nettoyé. Alors, restez là et regardez votre père faire la vaisselle jusqu'à ce que la vôtre soit faite. Il s'agit d'une distribution de synchronisation , car vous êtes bloqué pendant que vous attendez que la tâche soit terminée.
Et enfin, disons que votre maman décide d'aider votre père et le rejoint pour faire la vaisselle. Maintenant, la file d'attente devient une file d'attente simultanée puisqu'ils peuvent nettoyer plusieurs plats en même temps; mais notez que vous pouvez toujours décider d'y attendre ou de retourner dans votre chambre, quel que soit leur fonctionnement.
J'espère que cela t'aides
la source
1. Je lis que des files d'attente en série sont créées et utilisées pour exécuter les tâches les unes après les autres. Cependant, que se passe-t-il si: - • Je crée une file d'attente série • J'utilise dispatch_async (sur la file d'attente série que je viens de créer) trois fois pour distribuer trois blocs A, B, C
RÉPONSE : - Les trois blocs exécutés l'un après l'autre, j'ai créé un exemple de code qui aide à comprendre.
la source