Frage Gleichzeitige vs serielle Warteschlangen in GCD


Ich habe Probleme, die parallelen und seriellen Warteschlangen in GCD vollständig zu verstehen. Ich habe einige Probleme und hoffe, dass jemand mir klar und auf den Punkt antworten kann.

  1. Ich lese, dass serielle Warteschlangen erstellt und verwendet werden, um Aufgaben nacheinander auszuführen. Was passiert jedoch, wenn

    • Ich erstelle eine serielle Warteschlange
    • ich benutze dispatch_async (in der seriellen Warteschlange, die ich gerade erstellt habe) dreimal, um drei Blöcke A, B, C zu versenden

    Werden die drei Blöcke ausgeführt:

    • in der Reihenfolge A, B, C, weil die Warteschlange seriell ist

      ODER

    • gleichzeitig (in der gleichen Zeit auf paralel Themen), weil ich ASYNC Versand verwendet
  2. Ich lese, dass ich verwenden kann dispatch_sync in gleichzeitigen Warteschlangen, um Blöcke nacheinander auszuführen. In diesem Fall, WARUM sind serielle Warteschlangen überhaupt vorhanden, da ich immer eine gleichzeitige Warteschlange verwenden kann, in der ich SYNCHRONOUSLY so viele Blöcke versenden kann, wie ich möchte?

    Danke für jede gute Erklärung!


75
2017-10-04 10:47


Ursprung


Antworten:


Ein einfaches Beispiel: Sie haben einen Block, dessen Ausführung eine Minute dauert. Sie fügen es zu einer Warteschlange aus dem Hauptthread hinzu. Schauen wir uns die vier Fälle an.

  • async - gleichzeitig: Der Code wird auf einem Hintergrundthread ausgeführt. Die Steuerung kehrt sofort zum Hauptthread (und zur Benutzeroberfläche) zurück. Der Block kann nicht davon ausgehen, dass dies der einzige Block ist, der in dieser Warteschlange ausgeführt wird
  • async - seriell: Der Code wird auf einem Hintergrundthread ausgeführt. Die Steuerung kehrt sofort zum Hauptthread zurück. Der Block kann Angenommen, es ist der einzige Block, der in dieser Warteschlange ausgeführt wird
  • sync - concurrent: Der Code wird auf einem Hintergrundthread ausgeführt, aber der Hauptthread wartet darauf, dass er beendet wird und blockiert alle Aktualisierungen der Benutzeroberfläche. Der Block kann nicht davon ausgehen, dass es der einzige Block ist, der in dieser Warteschlange ausgeführt wird (ich hätte vor einigen Sekunden einen weiteren Block mit async hinzufügen können)
  • sync - seriell: Der Code wird auf einem Hintergrundthread ausgeführt, aber der Hauptthread wartet darauf, dass er beendet wird und blockiert alle Aktualisierungen der Benutzeroberfläche. Der Block kann Angenommen, es ist der einzige Block, der in dieser Warteschlange ausgeführt wird

Offensichtlich würden Sie keines der beiden letzten für lange laufende Prozesse verwenden. Normalerweise sehen Sie es, wenn Sie versuchen, die Benutzeroberfläche (immer im Hauptthread) von etwas zu aktualisieren, das möglicherweise in einem anderen Thread ausgeführt wird.


138
2017-10-04 11:12



Hier sind ein paar Experimente, die ich gemacht habe, um mich über diese zu verstehen serial, concurrent Warteschlangen mit 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")
        }
    }
}

Die Aufgabe wird in einem anderen Thread (außer dem Hauptthread) ausgeführt, wenn Sie Async in GCD verwenden. Async bedeutet, nächste Zeile ausführen, nicht warten, bis der Block ausgeführt wird, was zu nicht blockierendem Haupt-Thread und Haupt-Warteschlange führt.       Seit ihrer seriellen Warteschlange werden alle in der Reihenfolge ausgeführt, in der sie der seriellen Warteschlange hinzugefügt werden. Tasks, die seriell ausgeführt werden, werden immer einzeln durch den einzelnen Thread, der der Warteschlange zugeordnet ist, ausgeführt.

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")
        }
    }
}

Die Aufgabe kann im Hauptthread ausgeführt werden, wenn Sie die Synchronisierung in GCD verwenden. Sync führt einen Block für eine bestimmte Warteschlange aus und wartet darauf, dass sie abgeschlossen wird, wodurch der Hauptthread oder die Hauptwarteschlange blockiert wird. Da die Hauptwarteschlange warten muss, bis der abgesendete Block abgeschlossen ist, ist der Hauptthread verfügbar, um Blöcke aus anderen Warteschlangen zu verarbeiten Hauptwarteschlange. Daher besteht die Möglichkeit, dass der Code, der in der Hintergrundwarteschlange ausgeführt wird, tatsächlich im Hauptthread ausgeführt wird       Seit ihrer seriellen Warteschlange werden alle in der Reihenfolge ausgeführt, in der sie hinzugefügt werden (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")
    }
}

Die Aufgabe wird im Hintergrund-Thread ausgeführt, wenn Sie in GCD async verwenden. Async bedeutet, dass die nächste Zeile ausgeführt wird. Warten Sie nicht, bis der Block ausgeführt wird, was zu einem nicht blockierenden Hauptthread führt.       Denken Sie daran, dass die Aufgaben in der Warteschlange für gleichzeitige Verarbeitung in der Reihenfolge verarbeitet werden, in der sie zur Warteschlange hinzugefügt werden, jedoch mit verschiedenen Threads, die an die Warteschlange angehängt sind   Warteschlange. Denken Sie daran, dass sie die Aufgabe nicht als Auftrag abschließen sollen   Sie werden der Queue hinzugefügt. Die Reihenfolge der Aufgabe unterscheidet sich jedes Mal   Threads werden als notwendigerweise automatisch erstellt. Tasks werden parallel ausgeführt. Mit mehr als   dass (maxConcurrentOperationCount) erreicht ist, werden sich einige Tasks verhalten   als eine serielle, bis ein Thread frei ist.

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")
    }
}

Die Aufgabe kann im Hauptthread ausgeführt werden, wenn Sie die Synchronisierung in GCD verwenden. Sync führt einen Block für eine bestimmte Warteschlange aus und wartet darauf, dass sie abgeschlossen wird, wodurch der Hauptthread oder die Hauptwarteschlange blockiert wird. Da die Hauptwarteschlange warten muss, bis der abgesendete Block abgeschlossen ist, ist der Hauptthread verfügbar, um Blöcke aus anderen Warteschlangen zu verarbeiten Hauptwarteschlange. Daher besteht die Möglichkeit, dass der Code, der in der Hintergrundwarteschlange ausgeführt wird, tatsächlich im Hauptthread ausgeführt wird.         Seit der gleichzeitigen Warteschlange werden Tasks möglicherweise nicht in der Reihenfolge beendet, in der sie der Warteschlange hinzugefügt wurden. Bei synchronem Betrieb ist es jedoch möglich, obwohl sie von verschiedenen Threads verarbeitet werden können. Es verhält sich also wie die serielle Warteschlange.

Hier ist eine Zusammenfassung dieser Experimente

Denken Sie daran, dass Sie mit GCD nur Aufgaben zur Warteschlange hinzufügen und Aufgaben aus dieser Warteschlange ausführen. Warteschlange leitet Ihre Aufgabe entweder im Haupt- oder im Hintergrund-Thread ab, je nachdem, ob der Vorgang synchron oder asynchron ist. Warteschlangentypen sind Serial, Concurrent, Main Dispatch Queue. Alle von Ihnen ausgeführten Aufgaben werden standardmäßig von der Hauptversandwarteschlange ausgeführt. Es gibt bereits vier vordefinierte globale gleichzeitige Warteschlangen für Ihre Anwendung und eine Hauptwarteschlange (DispatchQueue.main) .You Sie können auch manuell eine eigene Warteschlange erstellen und Aufgaben aus dieser Warteschlange ausführen.

Benutzeroberfläche Die zugehörige Aufgabe sollte immer vom Hauptthread aus ausgeführt werden, indem die Aufgabe an die Hauptwarteschlange übergeben wird DispatchQueue.main.sync/async wohingegen netzwerkbezogene / -intensive Operationen immer asynchron durchgeführt werden sollten, unabhängig davon, ob Sie Haupt oder Hintergrund verwenden

BEARBEITEN: Es gibt jedoch Fälle, in denen Sie Netzwerkaufrufe synchron in einem Hintergrundthread ausführen müssen, ohne die Benutzeroberfläche einzufrieren (z. B. OAuth-Token aktualisieren und warten, wenn dies erfolgreich ist oder nicht). Sie müssen diese Methode innerhalb eines asynchronen Vorgangs umbrechen werden in der Reihenfolge ausgeführt und ohne Haupt-Thread blockiert.

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: Sie können ein Demo-Video ansehen Hier


82
2018-04-04 09:25



Wenn ich richtig verstehe, wie GCD funktioniert, denke ich, dass es zwei Arten gibt DispatchQueue, serial und concurrentZur gleichen Zeit gibt es zwei Wege wie DispatchQueue Senden Sie seine Aufgaben, die zugewiesen closureDer erste ist asyncund das andere ist sync. Diese zusammen bestimmen, wie die Schließung (Aufgabe) tatsächlich ausgeführt wird.

ich habe das gefunden serial und concurrentbedeuten, wie viele Threads die Warteschlange verwenden kann, serial bedeutet eins, während concurrent bedeutet viele. Und sync und async bedeutet, dass die Aufgabe ausgeführt wird, auf welchem ​​Thread, dem Thread des Aufrufers oder dem dieser Warteschlange zugrunde liegenden Thread, sync bedeutet laufen auf Anrufer Thread während async bedeutet laufen auf dem zugrunde liegenden Thread.

Der folgende Code ist experimentell und kann auf Xcode playground ausgeführt werden.

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

Ich hoffe, es kann hilfreich sein.


4