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