cocoa - usando dispatch_sync en Grand Central Dispatch
ios ios4 (8)
¿Alguien puede explicar con casos de uso realmente claros para qué es el propósito de dispatch_sync
en GCD
? No puedo entender dónde y por qué tendría que usar esto.
¡Gracias!
Aquí hay un ejemplo a mitad de camino realista. Tienes 2000 archivos zip que deseas analizar en paralelo. Pero la biblioteca zip no es segura para subprocesos. Por lo tanto, todo el trabajo que toca la biblioteca zip entra en la cola unzipQueue
. (El ejemplo está en Ruby, pero todas las llamadas se asignan directamente a la biblioteca de C. "apply", por ejemplo, maps a 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
Lo usa cuando quiere ejecutar un bloque y esperar los resultados.
Un ejemplo de esto es el patrón donde está utilizando una cola de despacho en lugar de bloqueos para la sincronización. Por ejemplo, supongamos que tiene una NSMutableArray a compartida, con acceso mediado por la cola de despacho q
. Un hilo de fondo se puede agregar a la matriz (asincrónico), mientras que el hilo de primer plano está sacando el primer elemento (sincrónicamente):
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
}
});
Primero comprende su hermano dispatch_async
//Do something
dispatch_async(queue, ^{
//Do something else
});
//Do More Stuff
Utiliza dispatch_async
para crear un nuevo hilo. Cuando haces eso, el hilo actual no se detiene. Eso significa que //Do More Stuff
se puede ejecutar antes de //Do something else
termine
¿Qué sucede si quieres que se detenga el hilo actual?
No usas despacho en absoluto. Simplemente escribe el código normalmente
//Do something
//Do something else
//Do More Stuff
Ahora, supongamos que quiere hacer algo en un subproceso DIFERENTE y, sin embargo, esperar como si y asegurarse de que las cosas se hagan de forma consecutiva .
Hay muchas razones para hacer esto. La actualización de la interfaz de usuario, por ejemplo, se realiza en el hilo principal.
Ahí es donde usas dispatch_sync
//Do something
dispatch_sync(queue, ^{
//Do something else
});
//Do More Stuff
Aquí tienes //Do something
//Do something else
y //Do More stuff
consecutiva, aunque //Do something else
en un hilo diferente.
Usualmente, cuando las personas usan diferentes hilos, el propósito es que algo se pueda ejecutar sin esperar. Supongamos que desea descargar una gran cantidad de datos pero desea mantener la interfaz de usuario sin problemas.
Por lo tanto, dispatch_sync se usa raramente. Pero está ahí. Yo personalmente nunca usé eso. ¿Por qué no solicitar un código de muestra o proyecto que sí utiliza dispatch_sync?
Si quiere algunas muestras de uso práctico, mire esta pregunta mía:
¿Cómo puedo resolver este punto muerto que ocurre de vez en cuando?
Lo resuelvo asegurando que mi main managedObjectContext se crea en el hilo principal. El proceso es muy rápido y no me importa esperar. No esperar significa que tendré que lidiar con un montón de problemas de concurencia.
Necesito dispatch_sync porque es necesario hacer algún código en el hilo principal, que es el hilo diferente del que se está ejecutando el código.
Entonces, básicamente, si quiere que el código sea 1. Proceda como siempre. No debes preocuparte por las condiciones de carrera. Desea asegurarse de que el código esté completo antes de continuar. 2. Hecho en un hilo diferente
use dispatch_sync.
Si se infringe 1, use dispatch_async. Si se infringe 2, simplemente escriba el código como de costumbre.
Hasta ahora, solo hago esto una vez, es decir, cuando algo debe hacerse en el hilo principal.
Así que aquí está el código:
+(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]];
}
Utilicé la sincronización de despacho cuando estaba dentro de un envío asíncrono para indicar que la UI cambia de nuevo al hilo principal.
Mi bloque asíncrono se detiene un poco y sé que el hilo principal está al tanto de los cambios en la interfaz de usuario y los activará. Generalmente, esto se usa en un bloque de código de procesamiento que requiere cierto tiempo de CPU, pero aún quiero hacer cambios en la UI desde ese bloque. Actuar sobre los cambios de la interfaz de usuario en el bloque asíncrono es inútil ya que la interfaz de usuario se ejecuta en el hilo principal. Activarlos también como bloques asíncronos secundarios, o un auto delegado, hace que la interfaz de usuario solo los vea unos segundos más tarde y se ve tarde.
Bloque de ejemplo:
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 */});
}
});
dispatch_sync es semánticamente equivalente a un bloqueo mutex tradicional.
dispatch_sync(queue, ^{
//access shared resource
});
funciona igual que
pthread_mutex_lock(&lock);
//access shared resource
pthread_mutex_unlock(&lock);
dispatch_sync se usa principalmente dentro del bloque dispatch_async para realizar algunas operaciones en el hilo principal (como 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;
});
});
David Gelhar no dijo que su ejemplo funcionará solo porque silenciosamente creó cola serial (pasó NULL en dispatch_queue_create lo que es igual a DISPATCH_QUEUE_SERIAL).
Si desea crear una cola simultánea (para obtener toda la potencia multithread), su código provocará un bloqueo debido a la mutación NSArray (addObject :) durante la mutación (removeObjectAtIndex :) o incluso al acceso incorrecto (NSArray range beyond bounds). En ese caso, deberíamos usar la barrera para asegurar el acceso exclusivo a NSArray mientras ambos bloques se ejecutan. No solo excluye todas las demás escrituras en NSArray mientras se ejecuta, sino que también excluye todas las demás lecturas, lo que hace que la modificación sea segura.
El ejemplo de cola concurrente debería verse así:
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.