Gleichzeitige und serielle Warteschlangen in GCD

117

Ich habe Probleme, die gleichzeitigen und seriellen Warteschlangen in GCD vollständig zu verstehen. Ich habe einige Probleme und hoffe, dass mir jemand klar und genau 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 verwende dispatch_async(in der soeben erstellten seriellen Warteschlange) dreimal, um drei Blöcke A, B, C zu versenden

    Werden die drei Blöcke ausgeführt:

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

      ODER

    • gleichzeitig (zur gleichen Zeit auf Parralel-Threads), weil ich ASYNC-Versand verwendet habe
  2. Ich lese, dass ich dispatch_syncfür gleichzeitige Warteschlangen verwenden kann, um Blöcke nacheinander auszuführen. WARUM gibt es in diesem Fall überhaupt serielle Warteschlangen, da ich immer eine gleichzeitige Warteschlange verwenden kann, in der ich SYNCHRON so viele Blöcke versenden kann, wie ich möchte?

    Vielen Dank für jede gute Erklärung!

Bogdan Alexandru
quelle
Eine einfache gute Voraussetzung Frage Versand Synchronisierung vs Async
Honey

Antworten:

216

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

  • asynchron - gleichzeitig: Der Code wird in einem Hintergrundthread ausgeführt. Die Steuerung kehrt sofort zum Hauptthread (und zur Benutzeroberfläche) zurück. Der Block kann nicht davon ausgehen, dass er der einzige Block ist, der in dieser Warteschlange ausgeführt wird
  • async - serial: Der Code wird in einem Hintergrundthread ausgeführt. Die Steuerung kehrt sofort zum Haupt-Thread zurück. Der Block kann davon ausgehen, dass er der einzige Block ist, der in dieser Warteschlange ausgeführt wird
  • Synchronisieren - gleichzeitig: Der Code wird in einem Hintergrundthread ausgeführt, der Hauptthread wartet jedoch auf den Abschluss und blockiert alle Aktualisierungen der Benutzeroberfläche. Der Block kann nicht davon ausgehen, dass er der einzige Block ist, der in dieser Warteschlange ausgeführt wird (ich hätte einige Sekunden zuvor einen weiteren Block mit Async hinzufügen können).
  • sync - serial: Der Code wird in einem Hintergrundthread ausgeführt, der Hauptthread wartet jedoch auf den Abschluss und blockiert alle Aktualisierungen der Benutzeroberfläche. Der Block kann davon ausgehen, dass er der einzige Block ist, der in dieser Warteschlange ausgeführt wird

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

Stephen Darlington
quelle
14
Sie sagen mir also, dass: (1) der Typ der Warteschlange (conc oder serial) das EINZIGE Element ist, das entscheidet, ob die Aufgaben in der richtigen Reihenfolge oder parallel ausgeführt werden ;; (2) Der Versandtyp (synchron oder asynchron) sagt nur, ob die Ausführung geht oder nicht zur nächsten Anweisung? Ich meine, wenn ich eine Task SYNC versende, wird der Code blockiert, bis diese Task beendet ist, egal in welcher Warteschlange sie ausgeführt wird?
Bogdan Alexandru
13
@ BogdanAlexandru Richtig. Die Warteschlange bestimmt die Ausführungsrichtlinie und nicht, wie Sie den Block in die Warteschlange stellen. Die Synchronisierung wartet auf den Abschluss des Blocks, die asynchrone nicht.
Jano
2
@swiftBUTCHER Bis zu einem gewissen Punkt ja. Wenn Sie eine Warteschlange erstellen, können Sie die maximale Anzahl von Threads angeben. Wenn Sie weniger Aufgaben hinzufügen, werden diese parallel ausgeführt. Mit mehr als dem bleiben einige Aufgaben in einer Warteschlange, bis Kapazität verfügbar ist.
Stephen Darlington
2
@PabloA., Der Hauptthread ist eine serielle Warteschlange, es gibt also nur zwei Fälle. Darüber hinaus ist es genau das gleiche. Async kehrt sofort zurück (und der Block wird wahrscheinlich am Ende der aktuellen Ausführungsschleife ausgeführt). Das Hauptproblem ist, wenn Sie vom Hauptthread zum Hauptthread synchronisieren. In diesem Fall erhalten Sie einen Deadlock.
Stephen Darlington
1
@ShauketSheikh Nein. Der Hauptthread ist eine serielle Warteschlange, aber nicht alle seriellen Warteschlangen sind der Hauptthread. Im vierten Punkt würde der Haupt-Thread blockieren und darauf warten, dass ein anderer Thread seine Arbeit konkurriert. Wenn die serielle Warteschlange der Hauptthread wäre, würden Sie einen Deadlock bekommen.
Stephen Darlington
122

Hier sind einige Experimente, die ich durchgeführt habe, um mich über diese zu informieren serial , concurrentWarteschlangen 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 in GCD asynchron verwenden. Async bedeutet, dass die nächste Zeile ausgeführt wird. Warten Sie nicht, bis der Block ausgeführt wird. Dies führt dazu, dass der Hauptthread und die Hauptwarteschlange nicht blockiert werden. Seit der seriellen Warteschlange werden alle in der Reihenfolge ausgeführt, in der sie zur seriellen Warteschlange hinzugefügt wurden. Seriell ausgeführte Aufgaben werden immer einzeln von dem einzelnen Thread ausgeführt, der der Warteschlange zugeordnet ist.

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 wird möglicherweise im Hauptthread ausgeführt, wenn Sie die Synchronisierung in GCD verwenden. Bei der Synchronisierung wird ein Block in einer bestimmten Warteschlange ausgeführt und auf dessen Abschluss gewartet, wodurch der Hauptthread oder die Hauptwarteschlange blockiert wird. Da die Hauptwarteschlange warten muss, bis der versendete Block abgeschlossen ist, steht der Hauptthread zur Verfügung, um Blöcke aus anderen Warteschlangen als der zu verarbeiten Hauptwarteschlange. Daher besteht die Möglichkeit, dass der in der Hintergrundwarteschlange ausgeführte Code tatsächlich im Hauptthread ausgeführt wird. Seit seiner 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 Hintergrundthread ausgeführt, wenn Sie in GCD asynchron 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 in der gleichzeitigen Warteschlange Aufgaben in der Reihenfolge verarbeitet werden, in der sie zur Warteschlange hinzugefügt werden, jedoch mit unterschiedlichen Threads, die an die Warteschlange angehängt sind. Denken Sie daran, dass sie die Aufgabe nicht in der Reihenfolge beenden sollen, in der sie der Warteschlange hinzugefügt wurden. Die Reihenfolge der Aufgaben unterscheidet sich jedes Mal, wenn Threads als unbedingt automatisch erstellt werden. Aufgaben werden parallel ausgeführt. Wenn mehr als das (maxConcurrentOperationCount) erreicht ist, verhalten sich einige Aufgaben seriell, 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 wird möglicherweise im Hauptthread ausgeführt, wenn Sie die Synchronisierung in GCD verwenden. Bei der Synchronisierung wird ein Block in einer bestimmten Warteschlange ausgeführt und auf dessen Abschluss gewartet, wodurch der Hauptthread oder die Hauptwarteschlange blockiert wird. Da die Hauptwarteschlange warten muss, bis der versendete Block abgeschlossen ist, steht der Hauptthread zur Verfügung, um Blöcke aus anderen Warteschlangen als der zu verarbeiten Hauptwarteschlange. Daher besteht die Möglichkeit, dass der in der Hintergrundwarteschlange ausgeführte Code tatsächlich im Hauptthread ausgeführt wird. Seit der gleichzeitigen Warteschlange werden Aufgaben möglicherweise nicht in der Reihenfolge beendet, in der sie der Warteschlange hinzugefügt wurden. Bei synchronem Betrieb ist dies jedoch der Fall, obwohl sie möglicherweise von verschiedenen Threads verarbeitet werden. Es verhält sich also so, als wäre dies 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. Die Warteschlange sendet Ihre Aufgabe entweder im Haupt- oder im Hintergrundthread, je nachdem, ob der Vorgang synchron oder asynchron ist. Warteschlangentypen sind Seriell, Gleichzeitig, Hauptversandwarteschlange. Alle von Ihnen ausgeführten Aufgaben werden standardmäßig über die Hauptversandwarteschlange ausgeführt. Es gibt bereits vier vordefinierte globale gleichzeitige Warteschlangen für Ihre Anwendung und eine Hauptwarteschlange (DispatchQueue.main) Sie können auch manuell eine eigene Warteschlange erstellen und Aufgaben aus dieser Warteschlange ausführen.

UI-bezogene Aufgaben sollten immer vom Hauptthread aus ausgeführt werden, indem die Aufgabe an die Hauptwarteschlange gesendet wird DispatchQueue.main.sync/async während netzwerkbezogene / schwere Vorgänge immer asynchron ausgeführt werden sollten, davon, welchen Thread Sie entweder als Haupt- oder als Hintergrund verwenden

BEARBEITEN: Es gibt jedoch Fälle, in denen Sie Netzwerkaufrufvorgänge synchron in einem Hintergrundthread ausführen müssen, ohne die Benutzeroberfläche einzufrieren (z. B. OAuth-Token aktualisieren und warten, ob dies erfolgreich ist oder nicht). Sie müssen diese Methode in einen asynchronen Vorgang einbinden Operationen werden in der Reihenfolge und ohne Blockieren des Hauptthreads ausgeführt.

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 Demo - Video sehen hier

LC 웃
quelle
Tolle Demonstration ... Warten Sie in der nächsten Zeile nicht, bis der Block ausgeführt wird, was zu einem nicht blockierenden Hauptthread führt. Wenn Sie Haltepunkte für einen Hintergrundthread verwenden, springt dieser zu, }da er in diesem Moment wirklich nicht ausgeführt wird
Honey
@Dieser faule iOS-Typ 웃 Ich verstehe den Unterschied zwischen asynchroner gleichzeitiger und asynchroner serieller Serie immer noch nicht. Was bedeutet die Verwendung von beidem? Beide laufen im Hintergrund und stören die Benutzeroberfläche nicht. Und warum sollten Sie jemals die Synchronisierung verwenden? Ist nicht alles Code synchronisiert. einer nach dem anderen?
Eonist
1
@ GitSyncApp Sie können das Video hier sehen
Anish Parajuli 28
@ Dieser faule iOS-Typ 웃: Danke dafür. Ich habe auf slack swift-lang gepostet. Wäre 👌 Wenn Sie auch eine über DispatchGroup und DispatchWorkItem erstellen könnten. : D
Eonist
Ich habe deine letzte getestet, die concurrentQueue.syncvon doLongSyncTaskInConcurrentQueue()Funktion, sie druckt den Haupt-Thread, Task will run in different threadscheint nicht wahr zu sein.
gabbler
53

Zunächst ist es wichtig, den Unterschied zwischen Threads und Warteschlangen zu kennen und zu wissen, was GCD wirklich tut. Wenn wir Versandwarteschlangen (über GCD) verwenden, stehen wir wirklich in der Warteschlange und nicht im Threading. Das Dispatch-Framework wurde speziell entwickelt, um uns vom Threading abzuhalten, da Apple zugibt, dass "die Implementierung einer korrekten Threading-Lösung extrem schwierig, wenn nicht [manchmal] unmöglich werden kann". Um Aufgaben gleichzeitig auszuführen (Aufgaben, bei denen die Benutzeroberfläche nicht eingefroren werden soll), müssen wir lediglich eine Warteschlange dieser Aufgaben erstellen und an GCD übergeben. Und GCD übernimmt das gesamte zugehörige Threading. Deshalb stellen wir uns nur in die Warteschlange.

Das zweite, was Sie sofort wissen müssen, ist, was eine Aufgabe ist. Eine Aufgabe ist der gesamte Code in diesem Warteschlangenblock (nicht in der Warteschlange, da wir einer Warteschlange jederzeit Dinge hinzufügen können, sondern innerhalb des Abschlusses, in dem wir ihn der Warteschlange hinzugefügt haben). Eine Aufgabe wird manchmal als Block bezeichnet, und ein Block wird manchmal als Aufgabe bezeichnet (sie werden jedoch häufiger als Aufgaben bezeichnet, insbesondere in der Swift-Community). Und egal wie viel oder wenig Code, der gesamte Code in den geschweiften Klammern wird als eine einzige Aufgabe betrachtet:

serialQueue.async {
    // this is one task
    // it can be any number of lines with any number of methods
}
serialQueue.async {
    // this is another task added to the same queue
    // this queue now has two tasks
}

Und es ist offensichtlich zu erwähnen, dass gleichzeitig einfach gleichzeitig mit anderen Dingen bedeutet und seriell bedeutet nacheinander (niemals gleichzeitig). Etwas zu serialisieren oder etwas in Serie zu setzen bedeutet einfach, es von Anfang bis Ende in seiner Reihenfolge von links nach rechts, von oben nach unten und ohne Unterbrechung auszuführen.

Es gibt zwei Arten von Warteschlangen: serielle und gleichzeitige, aber alle Warteschlangen sind relativ zueinander gleichzeitig . Die Tatsache, dass Sie Code "im Hintergrund" ausführen möchten, bedeutet, dass Sie ihn gleichzeitig mit einem anderen Thread (normalerweise dem Hauptthread) ausführen möchten. Daher führen alle seriellen oder gleichzeitigen Versandwarteschlangen ihre Aufgaben gleichzeitig relativ zu anderen Warteschlangen aus . Jede von Warteschlangen (von seriellen Warteschlangen) durchgeführte Serialisierung hat nur mit den Aufgaben in dieser einzelnen [seriellen] Versandwarteschlange zu tun (wie im obigen Beispiel, in dem sich zwei Aufgaben in derselben seriellen Warteschlange befinden; diese Aufgaben werden nacheinander ausgeführt der andere, niemals gleichzeitig).

SERIENWARTEN (häufig als private Versandwarteschlangen bezeichnet) garantieren die Ausführung von Aufgaben nacheinander von Anfang bis Ende in der Reihenfolge, in der sie dieser bestimmten Warteschlange hinzugefügt wurden. Dies ist die einzige Garantie für die Serialisierung in der Diskussion der Versandwarteschlangen- dass die spezifischen Aufgaben innerhalb einer bestimmten seriellen Warteschlange seriell ausgeführt werden. Serielle Warteschlangen können jedoch gleichzeitig mit anderen seriellen Warteschlangen ausgeführt werden, wenn es sich um separate Warteschlangen handelt, da wiederum alle Warteschlangen relativ zueinander gleichzeitig sind. Alle Aufgaben werden auf unterschiedlichen Threads ausgeführt, aber nicht jede Aufgabe wird garantiert auf demselben Thread ausgeführt (nicht wichtig, aber interessant zu wissen). Und das iOS-Framework enthält keine gebrauchsfertigen seriellen Warteschlangen. Sie müssen diese erstellen. Private (nicht globale) Warteschlangen sind standardmäßig seriell. So erstellen Sie eine serielle Warteschlange:

let serialQueue = DispatchQueue(label: "serial")

Sie können es über die Attributeigenschaft gleichzeitig ausführen:

let concurrentQueue = DispatchQueue(label: "concurrent", attributes: [.concurrent])

Wenn Sie der privaten Warteschlange jedoch keine weiteren Attribute hinzufügen, empfiehlt Apple, dass Sie nur eine der sofort einsatzbereiten globalen Warteschlangen verwenden (die alle gleichzeitig ausgeführt werden). Am Ende dieser Antwort sehen Sie eine andere Möglichkeit, serielle Warteschlangen (mithilfe der Zieleigenschaft) zu erstellen. Apple empfiehlt dies (für eine effizientere Ressourcenverwaltung). Die Kennzeichnung reicht jedoch vorerst aus.

CONCURRENT QUEUES (oft als globale Versandwarteschlangen bezeichnet) können Aufgaben gleichzeitig ausführen. Es wird jedoch garantiert, dass die Aufgaben in der Reihenfolge initiiert werden, in der sie zu dieser bestimmten Warteschlange hinzugefügt wurden. Im Gegensatz zu seriellen Warteschlangen wartet die Warteschlange jedoch nicht auf den Abschluss der ersten Aufgabe, bevor die zweite Aufgabe gestartet wird. Aufgaben (wie bei seriellen Warteschlangen) werden in unterschiedlichen Threads ausgeführt, und (wie bei seriellen Warteschlangen) wird nicht garantiert, dass jede Aufgabe auf demselben Thread ausgeführt wird (nicht wichtig, aber interessant zu wissen). Das iOS-Framework verfügt über vier sofort einsatzbereite Warteschlangen. Sie können eine gleichzeitige Warteschlange mithilfe des obigen Beispiels oder mithilfe einer der globalen Warteschlangen von Apple erstellen (was normalerweise empfohlen wird):

let concurrentQueue = DispatchQueue.global(qos: .default)

RETAIN-CYCLE RESISTANT: Versandwarteschlangen sind Objekte mit Referenzzählung, aber Sie müssen globale Warteschlangen nicht beibehalten und freigeben, da sie global sind. Daher wird das Beibehalten und Freigeben ignoriert. Sie können direkt auf globale Warteschlangen zugreifen, ohne sie einer Eigenschaft zuweisen zu müssen.

Es gibt zwei Möglichkeiten, Warteschlangen zu versenden: synchron und asynchron.

SYNC DISPATCHING bedeutet, dass der Thread, an den die Warteschlange gesendet wurde (der aufrufende Thread), nach dem Versenden der Warteschlange pausiert und wartet, bis die Ausführung der Aufgabe in diesem Warteschlangenblock abgeschlossen ist, bevor sie fortgesetzt wird. So versenden Sie synchron:

DispatchQueue.global(qos: .default).sync {
    // task goes in here
}

ASYNC DISPATCHING bedeutet, dass der aufrufende Thread nach dem Versenden der Warteschlange weiterhin ausgeführt wird und nicht darauf wartet, dass die Ausführung der Aufgabe in diesem Warteschlangenblock abgeschlossen ist. So senden Sie asynchron:

DispatchQueue.global(qos: .default).async {
    // task goes in here
}

Nun könnte man denken, dass zum Ausführen einer Aufgabe in serieller Form eine serielle Warteschlange verwendet werden sollte, und das ist nicht genau richtig. Um mehrere Aufgaben seriell auszuführen , sollte eine serielle Warteschlange verwendet werden, aber alle Aufgaben (für sich isoliert) werden seriell ausgeführt. Betrachten Sie dieses Beispiel:

whichQueueShouldIUse.syncOrAsync {
    for i in 1...10 {
        print(i)
    }
    for i in 1...10 {
        print(i + 100)
    }
    for i in 1...10 {
        print(i + 1000)
    }
}

Unabhängig davon, wie Sie diese Warteschlange konfigurieren (seriell oder gleichzeitig) oder versenden (synchronisieren oder asynchronisieren), wird diese Aufgabe immer seriell ausgeführt. Die dritte Schleife wird niemals vor der zweiten Schleife ausgeführt, und die zweite Schleife wird niemals vor der ersten Schleife ausgeführt. Dies gilt für jede Warteschlange, die einen beliebigen Versand verwendet. Wenn Sie mehrere Aufgaben und / oder Warteschlangen einführen, kommen Serialität und Parallelität wirklich ins Spiel.

Betrachten Sie diese beiden Warteschlangen, eine serielle und eine gleichzeitige:

let serialQueue = DispatchQueue(label: "serial")
let concurrentQueue = DispatchQueue.global(qos: .default)

Angenommen, wir versenden zwei gleichzeitige Warteschlangen asynchron:

concurrentQueue.async {
    for i in 1...5 {
        print(i)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
101
2
102
103
3
104
4
105
5

Ihre Ausgabe ist durcheinander (wie erwartet), aber beachten Sie, dass jede Warteschlange ihre eigene Aufgabe seriell ausführt. Dies ist das grundlegendste Beispiel für Parallelität - zwei Aufgaben, die gleichzeitig im Hintergrund in derselben Warteschlange ausgeführt werden. Jetzt machen wir die erste Serie:

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

101
1
2
102
3
103
4
104
5
105

Soll die erste Warteschlange nicht seriell ausgeführt werden? Es war (und so war das zweite). Was auch immer im Hintergrund passiert ist, ist für die Warteschlange nicht von Belang. Wir haben der seriellen Warteschlange gesagt, dass sie seriell ausgeführt werden soll, und das hat es getan ... aber wir haben ihr nur eine Aufgabe gegeben. Geben wir ihm nun zwei Aufgaben:

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
serialQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
2
3
4
5
101
102
103
104
105

Dies ist das grundlegendste (und einzig mögliche) Beispiel für die Serialisierung - zwei Aufgaben, die seriell (nacheinander) im Hintergrund (zum Hauptthread) in derselben Warteschlange ausgeführt werden. Wenn wir sie jedoch zu zwei separaten seriellen Warteschlangen gemacht haben (da sie im obigen Beispiel dieselbe Warteschlange sind), wird ihre Ausgabe erneut durcheinander gebracht:

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
serialQueue2.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
101
2
102
3
103
4
104
5
105

Und das habe ich gemeint, als ich sagte, dass alle Warteschlangen relativ zueinander gleichzeitig sind. Dies sind zwei serielle Warteschlangen, die ihre Aufgaben gleichzeitig ausführen (da es sich um separate Warteschlangen handelt). Eine Warteschlange kennt oder kümmert sich nicht um andere Warteschlangen. Kehren wir nun zu zwei seriellen Warteschlangen (derselben Warteschlange) zurück und fügen eine dritte Warteschlange hinzu, eine gleichzeitige:

serialQueue.async {
    for i in 1...5 {
        print(i)
    }
}
serialQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 1000)
    }
}

1
2
3
4
5
101
102
103
104
105
1001
1002
1003
1004
1005

Das ist etwas unerwartet. Warum hat die gleichzeitige Warteschlange gewartet, bis die seriellen Warteschlangen beendet sind, bevor sie ausgeführt wurden? Das ist keine Parallelität. Ihr Spielplatz zeigt möglicherweise eine andere Ausgabe, aber meine hat dies gezeigt. Dies zeigte sich, weil die Priorität meiner gleichzeitigen Warteschlange nicht hoch genug war, damit GCD ihre Aufgabe früher ausführen konnte. Wenn ich also alles gleich halte, aber die QoS der globalen Warteschlange (die Servicequalität, die einfach die Prioritätsstufe der Warteschlange darstellt) ändere let concurrentQueue = DispatchQueue.global(qos: .userInteractive), ist die Ausgabe wie erwartet:

1
1001
1002
1003
2
1004
1005
3
4
5
101
102
103
104
105

Die beiden seriellen Warteschlangen haben ihre Aufgaben seriell ausgeführt (wie erwartet), und die gleichzeitige Warteschlange hat ihre Aufgabe schneller ausgeführt, da ihr eine hohe Priorität (hohe QoS oder Servicequalität) zugewiesen wurde.

Zwei gleichzeitige Warteschlangen, wie in unserem ersten Druckbeispiel, zeigen einen durcheinandergebrachten Ausdruck (wie erwartet). Damit sie ordentlich seriell gedruckt werden, müssen beide dieselbe serielle Warteschlange haben (dieselbe Instanz dieser Warteschlange, nicht nur dasselbe Etikett) . Dann wird jede Aufgabe seriell in Bezug auf die andere ausgeführt. Eine andere Möglichkeit, sie zum seriellen Drucken zu bewegen, besteht darin, beide gleichzeitig zu halten, aber ihre Versandmethode zu ändern:

concurrentQueue.sync {
    for i in 1...5 {
        print(i)
    }
}
concurrentQueue.async {
    for i in 1...5 {
        print(i + 100)
    }
}

1
2
3
4
5
101
102
103
104
105

Denken Sie daran, dass das Versenden der Synchronisierung nur bedeutet, dass der aufrufende Thread wartet, bis die Aufgabe in der Warteschlange abgeschlossen ist, bevor Sie fortfahren. Die Einschränkung hierbei ist natürlich, dass der aufrufende Thread eingefroren ist, bis die erste Aufgabe abgeschlossen ist. Dies kann die Art und Weise sein, wie die Benutzeroberfläche ausgeführt werden soll oder nicht.

Aus diesem Grund können wir Folgendes nicht tun:

DispatchQueue.main.sync { ... }

Dies ist die einzig mögliche Kombination von Warteschlangen und Versandmethoden, die wir nicht ausführen können - synchrones Versenden in der Hauptwarteschlange. Und das liegt daran, dass wir die Hauptwarteschlange zum Einfrieren auffordern, bis wir die Aufgabe innerhalb der geschweiften Klammern ausführen ... die wir an die Hauptwarteschlange gesendet haben, die wir gerade eingefroren haben. Dies wird als Deadlock bezeichnet. Um es auf einem Spielplatz in Aktion zu sehen:

DispatchQueue.main.sync { // stop the main queue and wait for the following to finish
    print("hello world") // this will never execute on the main queue because we just stopped it
}
// deadlock

Eine letzte Sache zu erwähnen sind Ressourcen. Wenn wir einer Warteschlange eine Aufgabe zuweisen, findet GCD eine verfügbare Warteschlange aus dem intern verwalteten Pool. Für das Schreiben dieser Antwort stehen pro QOS 64 Warteschlangen zur Verfügung. Das mag viel erscheinen, kann aber schnell konsumiert werden, insbesondere von Bibliotheken von Drittanbietern, insbesondere von Datenbank-Frameworks. Aus diesem Grund hat Apple Empfehlungen zur Warteschlangenverwaltung (siehe Links unten). ein Wesen:

Anstatt private gleichzeitige Warteschlangen zu erstellen, senden Sie Aufgaben an eine der globalen gleichzeitigen Versandwarteschlangen. Legen Sie für serielle Aufgaben das Ziel Ihrer seriellen Warteschlange auf eine der globalen gleichzeitigen Warteschlangen fest. Auf diese Weise können Sie das serialisierte Verhalten der Warteschlange beibehalten und gleichzeitig die Anzahl der separaten Warteschlangen minimieren, die Threads erstellen.

Um dies zu tun, empfiehlt Apple, anstatt sie wie zuvor zu erstellen (was Sie immer noch können), serielle Warteschlangen wie folgt zu erstellen:

let serialQueue = DispatchQueue(label: "serialQueue", qos: .default, attributes: [], autoreleaseFrequency: .inherit, target: .global(qos: .default))

Zur weiteren Lektüre empfehle ich Folgendes:

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

bsod
quelle
7

Wenn ich mich richtig zu verstehen , wie GCD funktioniert, ich denke , es gibt zwei Arten von DispatchQueue, serialund concurrent, zur gleichen Zeit, es zwei Wege gibt , wie DispatchQueueihre Aufgaben versenden, die zugeordnete closure, erste ist async, und die andere ist sync. Diese zusammen bestimmen, wie der Abschluss (die Aufgabe) tatsächlich ausgeführt wird.

Ich habe das gefunden serialund concurrentmeine, wie viele Threads diese Warteschlange verwenden kann, serialbedeutet eins, während concurrentviele bedeutet. Und syncund asyncbedeutet, dass die Aufgabe ausgeführt wird, auf welchem ​​Thread, dem Thread des Aufrufers oder dem dieser Warteschlange zugrunde liegenden Thread, auf dem Thread des Aufrufers ausgeführt wird, syncwährend asyncauf dem zugrunde liegenden Thread ausgeführt wird.

Das Folgende ist experimenteller Code, der auf dem Xcode-Spielplatz ausgeführt werden kann.

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

Hoffe es kann hilfreich sein.

Keith
quelle
7

Ich denke das gerne mit dieser Metapher (Hier ist der Link zum Originalbild):

Papa wird Hilfe brauchen

Stellen wir uns vor, dein Vater macht den Abwasch und du hast gerade ein Glas Soda getrunken. Sie bringen das Glas zu Ihrem Vater, um es aufzuräumen, und stellen es neben das andere Gericht.

Jetzt erledigt Ihr Vater den Abwasch ganz alleine, also muss er ihn einzeln erledigen: Ihr Vater hier repräsentiert eine Serienwarteschlange .

Aber Sie sind nicht wirklich daran interessiert, dort zu stehen und zuzusehen, wie es aufgeräumt wird. Also lässt du das Glas fallen und gehst zurück in dein Zimmer: Dies wird als asynchroner Versand bezeichnet . Dein Vater könnte dich wissen lassen oder nicht, wenn er fertig ist, aber das Wichtigste ist, dass du nicht darauf wartest, dass das Glas aufgeräumt wird. Du gehst zurück in dein Zimmer, um Kinderkram zu erledigen.

Nehmen wir jetzt an, Sie haben immer noch Durst und möchten etwas Wasser auf demselben Glas haben, das zufällig Ihr Favorit ist, und Sie möchten es wirklich zurück, sobald es aufgeräumt ist. Also stehst du da und siehst zu, wie dein Vater den Abwasch macht, bis dein Geschirr fertig ist. Dies ist ein Synchronisierungsversand , da Sie blockiert sind, während Sie auf den Abschluss der Aufgabe warten.

Und zum Schluss sagen wir, deine Mutter beschließt, deinem Vater zu helfen und schließt sich ihm beim Abwasch an. Jetzt wird die Warteschlange zu einer gleichzeitigen Warteschlange, da mehrere Gerichte gleichzeitig gereinigt werden können. Beachten Sie jedoch, dass Sie immer noch entscheiden können, dort zu warten oder in Ihr Zimmer zurückzukehren, unabhängig davon, wie sie funktionieren.

Hoffe das hilft

Yunus Nedim Mehel
quelle
3

1. Ich lese, dass serielle Warteschlangen erstellt und verwendet werden, um Aufgaben nacheinander auszuführen. Was passiert jedoch, wenn: • ich eine serielle Warteschlange erstelle • ich dispatch_async (in der soeben erstellten seriellen Warteschlange) dreimal verwende, um drei Blöcke A, B, C zu versenden

ANTWORT : - Alle drei Blöcke werden nacheinander ausgeführt. Ich habe einen Beispielcode erstellt, der das Verständnis erleichtert.

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)
    }
}
CrazyPro007
quelle