Использование dispatch_sync в Grand Central Dispatch
Может кто-нибудь объяснить с очень ясными случаями использования, какова цель dispatch_sync
в GCD
для? Я не могу понять, где и почему я должен был бы использовать это.
Спасибо!
8 ответов
Вы используете его, когда хотите выполнить блок и ждать результатов.
Одним из примеров этого является шаблон, в котором вы используете очередь отправки вместо блокировок для синхронизации. Например, предположим, что у вас есть общий NSMutableArray a
с доступом, опосредованным диспетчерской очередью q
, Фоновый поток может добавляться к массиву (асинхронно), в то время как ваш основной поток вытягивает первый элемент (синхронно):
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
}
});
Сначала пойми своего брата dispatch_async
//Do something
dispatch_async(queue, ^{
//Do something else
});
//Do More Stuff
Ты используешь dispatch_async
создать новую тему. Когда вы это сделаете, текущий поток не остановится. Это означает //Do More Stuff
может быть выполнен раньше //Do something else
Конец
Что произойдет, если вы хотите остановить текущий поток?
Вы не используете рассылку вообще. Просто напиши код нормально
//Do something
//Do something else
//Do More Stuff
Теперь, скажем, вы хотите что-то сделать в РАЗНОМ потоке, и все же подождать, как будто, и убедиться, что вещи выполняются последовательно.
Есть много причин для этого. Например, обновление пользовательского интерфейса выполняется в основном потоке.
Вот где вы используете dispatch_sync
//Do something
dispatch_sync(queue, ^{
//Do something else
});
//Do More Stuff
Здесь вы получили //Do something
//Do something else
а также //Do More stuff
сделано последовательно, хотя //Do something else
сделано в другой теме.
Обычно, когда люди используют разные потоки, цель состоит в том, чтобы что-то могло быть выполнено без ожидания. Скажем, вы хотите загрузить большой объем данных, но хотите, чтобы интерфейс был гладким.
Следовательно, dispatch_sync используется редко. Но это там. Я лично никогда не использовал это. Почему бы не попросить пример кода или проекта, который использует dispatch_sync.
dispatch_sync семантически эквивалентен традиционной блокировке мьютекса.
dispatch_sync(queue, ^{
//access shared resource
});
работает так же, как
pthread_mutex_lock(&lock);
//access shared resource
pthread_mutex_unlock(&lock);
Дэвид Гелхар оставил недосказанным, что его пример будет работать только потому, что он тихо создал последовательную очередь (передал NULL в dispatch_queue_create, что равно DISPATCH_QUEUE_SERIAL).
Если вы хотите создать параллельную очередь (чтобы получить всю мощь многопоточности), его код приведет к сбою из-за мутации NSArray (addObject:) во время мутации (removeObjectAtIndex:) или даже из-за плохого доступа (диапазон NSArray за пределами границ). В этом случае мы должны использовать барьер для обеспечения монопольного доступа к NSArray во время работы обоих блоков. Он не только исключает все другие записи в NSArray во время его работы, но также исключает все другие операции чтения, что делает изменение безопасным.
Пример для параллельной очереди должен выглядеть так:
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.
Если вам нужны примеры практического использования, посмотрите на мой вопрос:
Как мне разрешить этот тупик, который иногда случается?
Я решаю это, гарантируя, что мой главный managedObjectContext создан в главном потоке. Процесс очень быстрый и я не против ждать. Не ждать означает, что мне придется столкнуться с множеством проблем с параллелизмом.
Мне нужен dispatch_sync, потому что некоторый код должен выполняться в основном потоке, который отличается от потока, в котором выполняется код.
В общем, если вы хотите, чтобы код равнялся 1. Действуйте как обычно. Вы не хотите беспокоиться о состоянии гонки. Вы хотите убедиться, что код завершен, прежде чем двигаться дальше. 2. Сделано в другой теме
используйте dispatch_sync.
Если 1 нарушено, используйте dispatch_async. Если 2 нарушено, просто напишите код, как обычно.
Пока что я делаю это только один раз, а именно, когда нужно что-то сделать в главном потоке.
Итак, вот код:
+(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]];
}
dispatch_sync в основном используется внутри блока dispatch_async для выполнения некоторых операций в основном потоке (например, обновление пользовательского интерфейса).
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;
});
});
Вот наполовину реалистичный пример. У вас есть 2000 zip-файлов, которые вы хотите анализировать параллельно. Но библиотека zip не является поточно-ориентированной. Поэтому вся работа, касающаяся библиотеки zip, идет в unzipQueue
очередь. (Пример написан на Ruby, но все вызовы сопоставляются непосредственно с библиотекой C. "Apply", например, сопоставляет 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
Я использовал диспетчерскую синхронизацию, когда находился внутри асинхронной диспетчеризации, чтобы сигнализировать об изменениях пользовательского интерфейса обратно в основной поток.
Мой асинхронный блок лишь немного сдерживается, и я знаю, что основной поток знает об изменениях пользовательского интерфейса и будет их обрабатывать. Обычно используется в блоке обработки кода, который занимает некоторое время процессора, но я все еще хочу, чтобы изменения пользовательского интерфейса действовали из этого блока. Действие изменений пользовательского интерфейса в асинхронном блоке бесполезно, поскольку, я полагаю, пользовательский интерфейс работает в основном потоке. Кроме того, их действие в качестве вторичных асинхронных блоков или самостоятельного делегата приводит к тому, что пользовательский интерфейс видит их только через несколько секунд, и это выглядит запоздалым.
Пример блока:
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 */});
}
});