Maximalwerte des Semaphors?
Zum Beispiel gibt es eine 1000-fache Schleife. Was ist der maximale Wert, um es schnell und effektiv zu machen und nicht zu einem Deadlock zu führen?
let group = DispatchGroup()
let queue = DispatchQueue(label: "com.num.loop", attributes: .concurrent)
let semaphore = DispatchSemaphore(value: 4)
for i in 1...1000 {
semaphore.wait()
group.enter()
queue.async(group: group, execute: {
doWork(i)
group.leave()
semaphore.signal()
})
}
group.notify(queue: DispatchQueue.main) {
// go on...
}
Antworten
Einige Beobachtungen:
Sie möchten niemals die maximale Anzahl von GCD-Worker-Threads pro QoS überschreiten. Wenn Sie dies überschreiten, kann es in Ihrer App zu Blockierungen kommen. Das letzte Mal, dass ich überprüft habe, war diese Grenze 64 Threads.
Allerdings ist es im Allgemeinen wenig vorteilhaft, die Anzahl der Kerne auf Ihrem Gerät zu überschreiten.
Oft ließen wir GCD die maximale Anzahl gleichzeitiger Threads ermitteln concurrentPerform, die automatisch für das Gerät optimiert werden. Außerdem werden keine Semaphoren oder Gruppen mehr benötigt, was häufig zu weniger überladenem Code führt:
DispatchQueue.global().async { DispatchQueue.concurrentPerform(iterations: 1000) { i in doWork(i) } DispatchQueue.main.async { // go on... } }Das
concurrentPerformwird die 1.000 Iterationen parallel laufen , aber die Anzahl der gleichzeitigen Threads auf ein angemessenes Niveau für Ihr Gerät zu begrenzen, wodurch die Notwendigkeit für die Semaphore zu beseitigen. IstconcurrentPerformaber selbst synchron und wird erst fortgesetzt, wenn alle Iterationen abgeschlossen sind, sodass die Versandgruppe nicht mehr benötigt wird. Versenden Sie das Ganze alsoconcurrentPerformin eine Hintergrundwarteschlange. Wenn dies erledigt ist, führen Sie einfach Ihren „Abschlusscode“ aus (oder senden Sie diesen Code in Ihrem Fall zurück in die Hauptwarteschlange).Während ich
concurrentPerformoben argumentiert habe, funktioniert das nur, wenndoWorkes seine Aufgabe synchron ausführt (z. B. eine Rechenoperation). Wenn es etwas initiiert, das selbst asynchron ist, müssen wir auf diese Semaphor- / Gruppentechnik zurückgreifen. (Oder, vielleicht besser, verwenden Sie asynchrone OperationUnterklassen mit einer Warteschlange mit angemessener maxConcurrentOperationCountoder Kombinieren flatMap(maxPublishers:_:)mit angemessener Begrenzung der Anzahl).In Bezug auf einen angemessenen Schwellenwert gibt es in diesem Fall keine magische Zahl. Sie müssen nur einige empirische Tests durchführen, um ein angemessenes Gleichgewicht zwischen der Anzahl der Kerne und den möglichen anderen Vorgängen in Ihrer App zu finden. Beispielsweise verwenden wir für Netzwerkanforderungen häufig 4 oder 6 als maximale Anzahl, wobei nicht nur der verringerte Nutzen bei der Überschreitung dieser Anzahl berücksichtigt wird, sondern auch die Auswirkungen der Auswirkungen auf unseren Server, wenn Tausende von Benutzern zufällig zu viele gleichzeitig senden Anfragen zur gleichen Zeit.
In Bezug auf „schnell machen“ ist die Wahl, „wie viele Iterationen gleichzeitig ausgeführt werden dürfen“, nur ein Teil des Entscheidungsprozesses. Das kritischere Problem stellt schnell sicher, dass
doWorkgenügend Arbeit geleistet wird, um den bescheidenen Overhead zu rechtfertigen, der durch das gleichzeitige Muster entsteht.Wenn Sie beispielsweise ein Bild mit einer Größe von 1.000 × 1.000 Pixel verarbeiten, können Sie 1.000.000 Iterationen mit jeweils einem Pixel ausführen. Wenn Sie dies jedoch tun, stellen Sie möglicherweise fest, dass es tatsächlich langsamer ist als Ihre nicht gleichzeitige Wiedergabe. Stattdessen haben Sie möglicherweise 1.000 Iterationen, wobei jede Iteration 1.000 Pixel verarbeitet. Oder Sie haben 100 Iterationen, von denen jede 10.000 Pixel verarbeitet. Diese Technik, die als „Schritt“ bezeichnet wird, erfordert häufig ein wenig empirische Forschung, um das richtige Gleichgewicht zwischen der Anzahl der durchgeführten Iterationen und der jeweiligen Arbeit zu finden. (Übrigens kann dieses Schrittmuster häufig auch das Schwappen des Cache verhindern, ein Szenario, das auftreten kann, wenn mehrere Threads um benachbarte Speicheradressen kämpfen.)
In Bezug auf den vorherigen Punkt möchten wir häufig, dass diese verschiedenen Threads ihren Zugriff auf gemeinsam genutzte Ressourcen synchronisieren (um die Thread-Sicherheit zu gewährleisten). Diese Synchronisation kann zu Konflikten zwischen diesen Threads führen. Sie sollten sich also überlegen, wie und wann Sie diese Synchronisierung durchführen.
Anstatt mehrere Synchronisierungen zu verwenden
doWork, kann es sein, dass jede Iteration eine lokale Variable aktualisiert (wobei keine Synchronisierung erforderlich ist) und die synchronisierte Aktualisierung der gemeinsam genutzten Ressource nur dann durchführt, wenn die lokalen Berechnungen abgeschlossen sind. Es ist schwierig, diese Frage abstrakt zu beantworten, da sie weitgehend davon abhängt, wasdoWorkgerade getan wird, aber sie kann sich leicht auf die Gesamtleistung auswirken.