Verwenden von dispatch_sync in Grand Central Dispatch

74

Kann jemand mit wirklich klaren Anwendungsfällen erklären, wozu der Zweck von dispatch_syncin GCDdient? Ich kann nicht verstehen, wo und warum ich das verwenden müsste.

Vielen Dank!

Rasputin Jones
quelle

Antworten:

78

Sie verwenden es, wenn Sie einen Block ausführen und auf die Ergebnisse warten möchten.

Ein Beispiel hierfür ist das Muster, bei dem Sie eine Versandwarteschlange anstelle von Sperren für die Synchronisierung verwenden. Angenommen a, Sie haben ein freigegebenes NSMutableArray , dessen Zugriff über die Versandwarteschlange vermittelt wird q. Ein Hintergrund-Thread wird möglicherweise an das Array angehängt (asynchron), während Ihr Vordergrund-Thread das erste Element (synchron) abzieht:

NSMutableArray *a = [[NSMutableArray alloc] init];
// All access to `a` is via this dispatch queue!
dispatch_queue_t q = dispatch_queue_create("com.foo.samplequeue", NULL);

dispatch_async(q, ^{ [a addObject:something]; }); // append to array, non-blocking

__block Something *first = nil;            // "__block" to make results from block available
dispatch_sync(q, ^{                        // note that these 3 statements...
        if ([a count] > 0) {               // ...are all executed together...
             first = [a objectAtIndex:0];  // ...as part of a single block...
             [a removeObjectAtIndex:0];    // ...to ensure consistent results
        }
});
David Gelhar
quelle
1
Ich werde dies +1 geben, da es technisch korrekt ist, obwohl ich nicht viel Wert darauf lege, eine einzelne zu machen, dispatch_asyncgefolgt von einer dispatch_syncin derselben Warteschlange. Das gleiche Muster ist jedoch nützlich, wenn Sie mehrere gleichzeitige Jobs in einer anderen Warteschlange erzeugen und dann auf alle warten möchten.
Kperryua
Vielen Dank. Das macht langsam Sinn. Was ist, wenn ich mit dispatch_apply mehrere gleichzeitige Threads starten möchte, die unter gegenseitigem Ausschluss auf eine einzelne Ressource zugreifen? Wie mache ich das mit GCD? Ist die einzige Möglichkeit, eine dispatch_async mit serieller Warteschlange in meiner dispatch_apply zu verwenden? Gibt es eine Möglichkeit, dispatch_sync zu verwenden?
Rasputin Jones
@kperryua - Entschuldigung, wenn das Beispiel nicht klar war - die Idee ist, dass ein separater Thread mehrere dispatch_async's in die Warteschlange führt
David Gelhar
@ David Gelhar - Kein Problem. Ich erwähne nur andere, die auf der Suche sind.
Kperryua
9
Ich stelle es mir auch gerne so vor, als würde man -performSelector:onThread:withObject:waitUntilDone:oder verwenden performSelectorOnMainThread:withObject:waitUntilDone:und waitUntilDoneauf JA setzen.
Brad Larson
78

Verstehe zuerst seinen Bruder dispatch_async

//Do something
dispatch_async(queue, ^{
    //Do something else
});
//Do More Stuff

Sie verwenden dispatch_async, um einen neuen Thread zu erstellen. Wenn Sie dies tun, wird der aktuelle Thread nicht gestoppt. Das heißt, //Do More Stuffkann vor dem //Do something elseAbschluss ausgeführt werden

Was passiert, wenn der aktuelle Thread gestoppt werden soll?

Sie verwenden den Versand überhaupt nicht. Schreiben Sie den Code einfach normal

//Do something
//Do something else
//Do More Stuff

Angenommen, Sie möchten etwas in einem VERSCHIEDENEN Thread tun und warten, als ob und stellen Sie sicher, dass die Dinge nacheinander ausgeführt werden .

Dafür gibt es viele Gründe. Die Aktualisierung der Benutzeroberfläche erfolgt beispielsweise im Hauptthread.

Dort verwenden Sie dispatch_sync

//Do something
dispatch_sync(queue, ^{
    //Do something else
});
//Do More Stuff

Hier hast du //Do something //Do something elseund //Do More stuffgetan nacheinander obwohl //Do something elseauf einem anderen Thread getan.

Wenn Leute einen anderen Thread verwenden, besteht der gesamte Zweck normalerweise darin, dass etwas ausgeführt werden kann, ohne zu warten. Angenommen, Sie möchten große Datenmengen herunterladen, aber die Benutzeroberfläche reibungslos gestalten.

Daher wird dispatch_sync selten verwendet. Aber es ist da. Ich persönlich habe das nie benutzt. Fragen Sie nach einem Beispielcode oder Projekt, das dispatch_sync verwendet.

user4951
quelle
Das war eine großartige Antwort für mich, danke. Ein Beispiel für die Verwendung dispatch_syncist die Verwendung eines anderen asynchronen Prozesses als Rückruf. Beispielsweise kann die NSManagedObjectContext- performBlockMethode von Core Data sie am Ende des Blocks als Rückruf verwenden.
Guptron
16
Als GCD-Anfänger fand ich diesen Satz irreführend: "Sie verwenden dispatch_async, um einen neuen Thread zu erstellen". Nach dem, was ich bisher von GCD verstanden habe, wird durch das Aufrufen von dispatch_async nicht unbedingt ein neuer Thread erstellt. Das System übernimmt die Thread-Erstellung oder Zuordnung zu jeder Aufgabe in der Warteschlange, denke ich.
Aurelien Porte
Eigentlich benutze ich das jetzt viel. Ich kann Code im Hintergrundthread ausführen und_sync an den Hauptthread senden.
user4951
Das ist großartig - verstehe es jetzt wirklich. Vielen Dank!
Darkheartfelt
1
Abgesehen von einem offensichtlichen kleinen Fehler, der in den Kommentaren angegeben ist, ist diese Erklärung sehr klar und hilfreich, danke!
Ixx
26

dispatch_sync entspricht semantisch einer herkömmlichen Mutex-Sperre.

dispatch_sync(queue, ^{
    //access shared resource
});

funktioniert genauso wie

pthread_mutex_lock(&lock);
//access shared resource
pthread_mutex_unlock(&lock);
Catfish_Man
quelle
2
Dies gilt für die serielle Warteschlange, aber für die gleichzeitige Warteschlange sollten wir dispatch_barrier_async für den Schreibvorgang und dispatch_sync für den Lesevorgang verwenden.
Parag Bafna
4

David Gelhar hat nicht gesagt, dass sein Beispiel nur funktioniert, weil er stillschweigend eine serielle Warteschlange erstellt hat (in dispatch_queue_create wurde NULL übergeben, was DISPATCH_QUEUE_SERIAL entspricht).

Wenn Sie eine gleichzeitige Warteschlange erstellen möchten (um die gesamte Multithread-Leistung zu erhalten), führt sein Code aufgrund einer NSArray-Mutation (addObject :) während der Mutation (removeObjectAtIndex :) oder sogar eines schlechten Zugriffs (NSArray-Bereich über die Grenzen hinaus) zum Absturz. In diesem Fall sollten wir eine Barriere verwenden, um den exklusiven Zugriff auf das NSArray sicherzustellen, während beide Blöcke ausgeführt werden. Es schließt nicht nur alle anderen Schreibvorgänge in das NSArray aus, während es ausgeführt wird, sondern schließt auch alle anderen Lesevorgänge aus, wodurch die Änderung sicher wird.

Das Beispiel für eine gleichzeitige Warteschlange sollte folgendermaßen aussehen:

NSMutableArray *a = [[NSMutableArray alloc] init];
// All access to `a` is via this concurrent dispatch queue!
dispatch_queue_t q = dispatch_queue_create("com.foo.samplequeue", DISPATCH_QUEUE_CONCURRENT);

// append to array concurrently but safely and don't wait for block completion
dispatch_barrier_async(q, ^{ [a addObject:something]; }); 

__block Something *first = nil;
// pop 'Something first' from array concurrently and safely but wait for block completion...
dispatch_barrier_sync(q, ^{                        
        if ([a count] > 0) {               
             first = [a objectAtIndex:0];  
             [a removeObjectAtIndex:0];    
        }
});
// ... then here you get your 'first = [a objectAtIndex:0];' due to synchronised dispatch.
// If you use async instead of sync here, then first will be nil.
Krzysztof Przygoda
quelle
3

Wenn Sie einige Beispiele für den praktischen Gebrauch wünschen, schauen Sie sich diese Frage an:

Wie löse ich diesen gelegentlich auftretenden Stillstand?

Ich löse es, indem ich sicherstelle, dass mein Haupt-ManagedObjectContext im Haupt-Thread erstellt wird. Der Prozess ist sehr schnell und es macht mir nichts aus zu warten. Nicht warten bedeutet, dass ich mich mit vielen Problemen der Vertraulichkeit befassen muss.

Ich benötige dispatch_sync, da Code für den Hauptthread ausgeführt werden muss. Dies ist der andere Thread als der, in dem der Code ausgeführt wird.

Wenn Sie also möchten, dass der Code 1. Wie gewohnt vorgeht. Sie möchten sich keine Sorgen um die Rennbedingungen machen. Sie möchten sicherstellen, dass der Code vollständig ist, bevor Sie fortfahren. 2. Fertig auf einem anderen Thread

Verwenden Sie dispatch_sync.

Wenn 1 verletzt wird, verwenden Sie dispatch_async. Wenn 2 verletzt wird, schreiben Sie einfach den Code wie gewohnt.

Bisher mache ich das nur einmal, nämlich wenn etwas am Haupt-Thread gemacht werden muss.

Also hier ist der Code:

+(NSManagedObjectContext *)managedObjectContext {


    NSThread *thread = [NSThread currentThread];
    //BadgerNewAppDelegate *delegate = [BNUtilitiesQuick appDelegate];
    //NSManagedObjectContext *moc = delegate.managedObjectContext;

    if ([thread isMainThread]) {
        //NSManagedObjectContext *moc = [self managedObjectContextMainThread];
        return [self managedObjectContextMainThread];
    }
    else{
        dispatch_sync(dispatch_get_main_queue(),^{
            [self managedObjectContextMainThread];//Access it once to make sure it's there
        });
    }

    // a key to cache the context for the given thread
    NSMutableDictionary *managedObjectContexts =[self thread].managedObjectContexts;

    @synchronized(self)
    {
        if ([managedObjectContexts objectForKey:[self threadKey]] == nil ) {
            NSManagedObjectContext *threadContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
            threadContext.parentContext = [self managedObjectContextMainThread];
            //threadContext.persistentStoreCoordinator= [self persistentStoreCoordinator]; //moc.persistentStoreCoordinator;//  [moc persistentStoreCoordinator];
            threadContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy;
            [managedObjectContexts setObject:threadContext forKey:[self threadKey]];
        }
    }


    return [managedObjectContexts objectForKey:[self threadKey]];
}
user4951
quelle
2

dispatch_sync wird hauptsächlich im dispatch_async-Block verwendet, um einige Operationen am Hauptthread auszuführen (z. B. update ui).

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    //Update UI in main thread
    dispatch_sync(dispatch_get_main_queue(), ^{
      self.view.backgroundColor = color;
    });
});
Hari Kunwar
quelle
0

Hier ist ein halbwegs realistisches Beispiel. Sie haben 2000 Zip-Dateien, die Sie parallel analysieren möchten. Die Zip-Bibliothek ist jedoch nicht threadsicher. Daher werden alle Arbeiten, die die Zip-Bibliothek berühren, in die unzipQueueWarteschlange gestellt. (Das Beispiel ist in Ruby, aber alle Aufrufe werden direkt der C-Bibliothek zugeordnet. "Apply", z. B. Zuordnungen zu dispatch_apply (3) )

#!/usr/bin/env macruby -w

require 'rubygems'
require 'zip/zipfilesystem'

@unzipQueue = Dispatch::Queue.new('ch.unibe.niko.unzipQueue')
def extractFile(n)
    @unzipQueue.sync do
        Zip::ZipFile.open("Quelltext.zip") {   |zipfile|
            sourceCode = zipfile.file.read("graph.php")
        }
    end
end

Dispatch::Queue.concurrent.apply(2000) do |i|
   puts i if i % 200 == 0
   extractFile(i)
end
nes1983
quelle
9
Verwenden Sie Pseudocode, wenn Sie etwas erklären möchten. Ruby et al. Sind zu spezifisch und zu hoch.
Matt Melton
-1

Ich habe die Dispatch-Synchronisierung innerhalb eines asynchronen Dispatches verwendet, um zu signalisieren, dass Änderungen an der Benutzeroberfläche zurück zum Haupt-Thread erfolgen.

Mein asynchroner Block hält sich nur wenig zurück und ich weiß, dass der Hauptthread die Änderungen an der Benutzeroberfläche kennt und diese ausführen wird. Wird im Allgemeinen in einem Verarbeitungsblock von Code verwendet, der einige CPU-Zeit benötigt, aber ich möchte weiterhin Änderungen an der Benutzeroberfläche innerhalb dieses Blocks vornehmen. Das Ausführen der Änderungen an der Benutzeroberfläche im asynchronen Block ist nutzlos, da die Benutzeroberfläche meines Erachtens auf dem Hauptthread ausgeführt wird. Wenn Sie sie auch als sekundäre asynchrone Blöcke oder als Selbstdelegierter ausführen, werden sie von der Benutzeroberfläche nur wenige Sekunden später angezeigt und es sieht verspätet aus.

Beispielblock:

dispatch_queue_t myQueue = dispatch_queue_create("my.dispatch.q", 0);
dispatch_async(myQueue,
^{

    //  Do some nasty CPU intensive processing, load file whatever

         if (somecondition in the nasty CPU processing stuff)
         {
             //  Do stuff
             dispatch_sync(dispatch_get_main_queue(),^{/* Do Stuff that affects UI Here */});
         }

 });
Erdferkel
quelle