web-dev-qa-db-fra.com

Files d'attente simultanées vs séries dans GCD

J'ai du mal à comprendre parfaitement les files d'attente concurrentes et en série dans GCD. J'ai quelques problèmes et j'espère que quelqu'un pourra me répondre clairement et clairement.

  1. Je lis que les files d'attente en série sont créées et utilisées afin d'exécuter des 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 en série

      OR

    • en même temps (dans le même temps sur les threads parralel) parce que j'ai utilisé l'envoi ASYNC
  2. Je lis que je peux utiliser dispatch_sync sur des files d'attente simultanées pour exécuter des blocs les uns après les autres. Dans ce cas, POURQUOI existe-t-il même des files d'attente série, car je peux toujours utiliser une file d'attente simultanée où je peux envoyer SYNCHRONOUSEMENT autant de blocs que je le souhaite?

    Merci pour toute bonne explication!

84
Bogdan Alexandru

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.

  • async - concurrent: le code s'exécute sur un thread en arrière-plan. Le contrôle retourne immédiatement au thread principal (et à l'interface utilisateur). Le bloc ne peut pas supposer qu'il s'agit du seul bloc en cours d'exécution sur cette file d'attente.
  • async - série: le code s'exécute sur un thread en arrière-plan. Le contrôle revient immédiatement au thread principal. Le bloc peut suppose que c'est le seul bloc en cours d'exécution sur cette file d'attente
  • sync - concurrent: le code s'exécute sur un thread en arrière-plan mais le thread principal attend sa fin, bloquant ainsi les mises à jour de l'interface utilisateur. Le bloc ne peut pas supposer que c'est le seul bloc en cours d'exécution sur cette file d'attente (j'aurais pu ajouter un autre bloc en utilisant async quelques secondes auparavant)
  • sync-serial: le code s'exécute sur un thread en arrière-plan mais le thread principal attend sa fin, bloquant ainsi les mises à jour de l'interface utilisateur. Le bloc peut suppose que c'est le seul bloc en cours d'exécution sur cette file d'attente

Évidemment, 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 en cours d'exécution sur un autre thread.

165

Voici quelques expériences que j'ai effectuées pour me faire comprendre ces files d'attente serial, concurrent avec 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 tâche s'exécutera dans un autre thread (autre que le thread principal) lorsque vous utiliserez async dans GCD. Async signifie que l'exécution de la ligne suivante n'attend pas que le bloc soit exécuté, ce qui entraîne un thread principal et une file d'attente principale non bloquants . Depuis sa file d'attente série, toutes sont exécutées dans l'ordre dans lequel elles ont été ajoutées. Les tâches exécutées en série sont toujours exécutées une par une par le seul thread associé à la file d'attente.

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 tâche peut s'exécuter dans le thread principal lorsque vous utilisez la synchronisation dans GCD. Sync exécute un bloc sur une file d'attente donnée et attend qu'il se termine, ce qui bloque le thread principal ou la file d'attente principale. file d'attente principale. Il est donc possible que le code s'exécutant sur la file d'attente d'arrière-plan s'exécute effectivement sur le thread principal Depuis sa file d'attente série, tous sont exécutés dans l'ordre dans lequel ils ont été ajoutés (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 tâche s'exécutera dans un thread d'arrière-plan lorsque vous utiliserez async dans GCD. Async signifie que l'exécution de la ligne suivante n'attend pas que le bloc soit exécuté, ce qui entraîne un thread principal non bloquant . N'oubliez pas que dans une file d'attente simultanée, les tâches sont traitées dans l'ordre dans lequel elles ont été ajoutées à la file d'attente, mais que différents threads sont associés au fichier queue. Rappelez-vous qu'ils ne sont pas censés terminer la tâche en tant que commande ils sont ajoutés à la file d'attente. L'ordre des tâches diffère à chaque fois les threads sont créés de manière forcément automatique. Les tâches sont exécutées en parallèle. Avec plus de que (maxConcurrentOperationCount) est atteint, certaines tâches se comporteront en série jusqu'à ce qu'un fil soit 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 tâche peut s'exécuter dans le thread principal lorsque vous utilisez la synchronisation dans GCD. Sync exécute un bloc sur une file d'attente donnée et attend qu'il se termine, ce qui bloque le thread principal ou la file d'attente principale. file d'attente principale. Il est donc possible que le code s'exécutant sur la file d'attente d'arrière-plan s'exécute effectivement sur le thread principal . Depuis sa file d'attente simultanée, les tâches peuvent ne pas se terminer dans l'ordre dans lequel elles ont été ajoutées. Mais avec un fonctionnement synchrone, il le fait bien qu'ils puissent être traités par différents threads. Donc, il se comporte comme c'est la file d'attente série.

Voici un résumé de ces expériences

Rappelez-vous que lorsque vous utilisez GCD, vous n’ajoutez que des tâches à la file d’attente et que vous exécutez des tâches à 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 suivants: Série, Concurrente, File d'attente principale. Toutes les tâches que vous effectuez sont effectuées par défaut à partir de la file d'attente principale. Il existe déjà quatre files d'attente globales simultanées prédéfinies que votre application peut utiliser et une file d'attente principale (DispatchQueue.main). peut également créer manuellement votre propre file d'attente et effectuer des tâches à partir de cette file d'attente.

La tâche associée à l'interface utilisateur doit toujours être effectuée à partir du thread principal en la répartissant dans la file d'attente principale. L'utilitaire à main courte est DispatchQueue.main.sync/async, alors que les opérations liées au réseau/lourdes doivent toujours être effectuées de manière asynchrone, que le thread que vous utilisiez soit en arrière-plan soit important

EDIT: Cependant, dans certains cas, vous devez exécuter des opérations d'appels réseau de manière synchrone dans un thread en arrière-plan sans geler l'interface utilisateur (par exemple, régénérer le jeton OAuth et attendre s'il réussit ou non) .Vous devez envelopper cette méthode dans un environnement asynchrone. operation.This vos opérations lourdes sont exécutées dans l’ordre et sans bloquer le thread 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")
        }
    }
}

EDIT EDIT: Vous pouvez regarder la vidéo de démonstration ici

95
LC 웃

Si je comprends bien le fonctionnement de GCD, je pense qu’il existe deux types de DispatchQueue, serial et concurrent. En même temps, il y a deux façons dont DispatchQueue répartit ses tâches, le closure affecté, le premier est async et l’autre est sync. Ceux-ci ensemble déterminent comment la fermeture (tâche) est réellement exécutée.

J'ai trouvé que serial et concurrent désignent le nombre de threads que la file d'attente peut utiliser, serial en désigne un, alors que concurrent en désigne plusieurs. Et sync et async signifient que la tâche sera exécutée sur le thread, le thread de l'appelant ou le thread sous-jacent à cette file d'attente, sync signifie qu'il s'exécute sur le thread de l'appelant alors que async signifie qu'il s'exécute sur le thread sous-jacent.

Ce qui suit est un code expérimental pouvant être exécuté sur un terrain de jeu Xcode.

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()

J'espère que cela peut être utile.

4
Keith

J'aime penser cela en utilisant cette métaphore (voici le lien vers l'image d'origine):

Dad's gonna need some help

Imaginons que votre père fasse la vaisselle et que vous venez de boire 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, il va donc devoir le faire un par un: ton père représente une file d'attente en série .

Mais vous n'êtes pas vraiment intéressé à rester là et à regarder les choses se nettoyer. Alors, vous laissez tomber le verre et retournez dans votre chambre: il s’agit d’un envoi async . Votre père pourrait vous dire ou ne pas vous le faire savoir une fois qu'il l'a fait, mais le point important est que vous n'attendez pas que le verre soit nettoyé; tu retournes dans ta chambre pour faire, tu sais, des trucs de gamins.

Supposons maintenant que vous ayez encore soif et que vous souhaitiez avoir de l’eau sur le même verre que vous préférez, et que vous voulez vraiment la récupérer dès qu’elle aura été nettoyée. Vous restez donc là et regardez votre père faire la vaisselle jusqu'à ce que la vôtre soit terminée. Il s’agit d’une dépêche de synchronisation , car vous êtes bloqué pendant que vous attendez que la tâche soit terminée.

Et finalement, disons que votre mère 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 car ils peuvent nettoyer plusieurs plats en même temps; mais notez que vous pouvez toujours décider d’attendre ou de retourner dans votre chambre, quelle que soit leur méthode de travail.

J'espère que cela t'aides

2
Yunus Nedim Mehel
**1.    I'm reading that serial queues are created and used in order to execute tasks one after the other. However, what happens if:
    •   I create a serial queue
    •   I use dispatch_async (on the serial queue I just created) three times to dispatch three blocks A,B,C**
**ANSWER**:-
All three blocks executed one after the another. I have created one sample code that helps to understand

let serialQueue = DispatchQueue(label: "SampleSerialQueue")
//Block first
serialQueue.async {
    for i in 1...10{
        print("Serial - First operation",i)
    }
}
//Block second
serialQueue.async {
    for i in 1...10{
        print("Serial - Second operation",i)
    }
}
//Block Third
serialQueue.async {
    for i in 1...10{
        print("Serial - Third operation",i)
    }
}
0
CrazyPro007