76

Can anyone explain with really clear use cases what the purpose of dispatch_sync in GCD is for? I can't understand where and why I would have to use this.

Thanks!

Rasputin Jones
  • 1,427
  • 2
  • 16
  • 24

8 Answers8

78

First understand its brother dispatch_async

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

You use dispatch_async to create a new thread. When you do that, the current thread will not stop. That means //Do More Stuff may be executed before //Do something else finish

What happens if you want the current thread to stop?

You do not use dispatch at all. Just write the code normally

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

Now, say you want to do something on a DIFFERENT thread and yet wait as if and ensure that stuffs are done consecutively.

There are many reason to do this. UI update, for example, is done on main thread.

That's where you use dispatch_sync

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

Here you got //Do something //Do something else and //Do More stuff done consecutively even though //Do something else is done on a different thread.

Usually, when people use different thread, the whole purpose is so that something can get executed without waiting. Say you want to download large amount of data but you want to keep the UI smooth.

Hence, dispatch_sync is rarely used. But it's there. I personally never used that. Why not ask for some sample code or project that does use dispatch_sync.

Fry
  • 6,235
  • 8
  • 54
  • 93
user4951
  • 32,206
  • 53
  • 172
  • 282
  • This was great answer for me, thanks. An example of using `dispatch_sync` is from within another asynch process to use as a callback. For example, Core Data's NSManagedObjectContext `performBlock` method can use it at the end of the block as a callback. – abc123 Jul 07 '13 at 18:46
  • 16
    As a GCD beginner, I found this sentence misleading:"You use dispatch_async to create a new thread". From what I understood of GCD so far, calling dispatch_async does not necessarily create a new thread. The system will handle thread creation or attribution to each queued task I guess. – Aurelien Porte Oct 21 '13 at 18:10
  • Actually I uses this a lot now. I can execute code in background thread and dispatch_sync to main thread. – user4951 Nov 05 '13 at 10:08
  • This is GREAT - really understand it now. Thanks! – darkheartfelt Jun 14 '14 at 20:43
  • //Do something dispatch_sync(queue, ^{ //Do something else }); //Do More Stuff Here you got //Do something //Do something else and //Do More stuff done consecutively even though //Do something else is done on a different thread. This is not true, //Do something else doesn't necessarily execute in different thread, system will decide whether it will execute on same thread or a different thread. – russell Sep 27 '14 at 17:47
  • 1
    Besides an apparent little error, indicated in the comments, this explanation is very clear and helpful, thanks! – User Jan 17 '15 at 18:45
  • To create a queue (serial for example), you use `dispatch_queue_t queue = dispatch_create_queue("com.mydomain.myname.somequeuename", DISPATCH_QUEUE_SERIAL);` and then you can use it for synchronizing stuff like: `dispatch_async(queue, ^{ /* some code for the block */ });`. – Alejandro Iván Nov 24 '16 at 15:30
  • And the basic rule I follow: if reading to a variable, use `dispatch_sync()`: `__block NSUInteger myValue; dispatch_sync(queue, ^{ myValue = someValueSynced; });`. If writing, use `dispatch_async()` and call an update: `dispatch_async(queue, ^{ someValueSynced = something; dispatch_async(dispatch_get_main_queue(), ^{ /* update UI, something like tableView reloadData or so */ }); });` (the methods that read and update UI should be synced using `dispatch_sync()`, so this should be thread-safe). – Alejandro Iván Nov 24 '16 at 15:33
  • and as for that little mistake, I wonder how you can accomplish something asynchronously without creating a new thread? – user4951 Jan 19 '17 at 09:53
78

You use it when you want to execute a block and wait for the results.

One example of this is the pattern where you're using a dispatch queue instead of locks for synchronization. For example, assume you have a shared NSMutableArray a, with access mediated by dispatch queue q. A background thread might be appending to the array (async), while your foreground thread is pulling the first item off (synchronously):

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
        }
});
Duck
  • 34,902
  • 47
  • 248
  • 470
David Gelhar
  • 27,873
  • 3
  • 67
  • 84
  • 1
    I'll +1 this, since it's technically correct, although I there isn't much value in doing a single `dispatch_async` followed by a `dispatch_sync` on the same queue. However this same pattern is useful when you want to spawn multiple concurrent jobs on another queue and then wait for them all. – kperryua Jan 05 '11 at 19:57
  • Thanks. This is beginning to make sense. What if I want to start multiple concurrent threads using dispatch_apply that access a single resource with mutual exclusion. How do I do this with GCD? Is the only way is to use a dispatch_async with serial queue within my dispatch_apply? Is there a way to use dispatch_sync? – Rasputin Jones Jan 05 '11 at 20:56
  • @kperryua - sorry if the example wasn't clear - the idea is that the a separate thread would be doing multiple dispatch_async's to the queue – David Gelhar Jan 06 '11 at 01:20
  • @David Gelhar - No problem. Just making mention for others who come looking. – kperryua Jan 06 '11 at 06:01
  • @Rasputin Jones - It's still possible to use a regular mutex lock in a GCD queue, but you can also accomplish this with `dispatch_sync`: create a new queue dedicated to your shared resource. Inside the block you pass to `dispatch_apply`, use `dispatch_sync` with that queue and access the shared resource inside that block. You can use `__block` variables if you need to get any data back from that block (as long as you're careful not to let your shared resource leak out with it). – kperryua Jan 06 '11 at 06:09
  • 9
    I also like to think of it as being similar to using `-performSelector:onThread:withObject:waitUntilDone:` or `performSelectorOnMainThread:withObject:waitUntilDone:` and setting `waitUntilDone` to YES. – Brad Larson Jan 06 '11 at 18:42
  • Brad it's important to note that although performSelector:onThread:withObject:waitUntilDone: is similar it is not a replacement. There is no guarantee in performSelector: that your method will run all together. It's better to use dispatch_sync to guarantee that a dispatch_async thread will not be executed in-between your code. – Conor Mar 09 '14 at 01:37
  • is there any time that a `dispatch_sync` can't do what a lock can do? If so where? – mfaani Dec 27 '16 at 17:34
25

dispatch_sync is semantically equivalent to a traditional mutex lock.

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

works the same as

pthread_mutex_lock(&lock);
//access shared resource
pthread_mutex_unlock(&lock);
Catfish_Man
  • 41,261
  • 11
  • 67
  • 84
  • 2
    This is true for serial queue but for concurrent queue we should use dispatch_barrier_async for write operation and dispatch_sync for read operation. – Parag Bafna Dec 29 '14 at 07:26
5

David Gelhar left unsaid that his example will work only because he quietly created serial queue (passed NULL in dispatch_queue_create what is equal to DISPATCH_QUEUE_SERIAL).

If you wish create concurrent queue (to gain all of multithread power), his code will lead to crash because of NSArray mutation (addObject:) during mutation (removeObjectAtIndex:) or even bad access (NSArray range beyond bounds). In that case we should use barrier to ensure exclusive access to the NSArray while the both blocks run. Not only does it exclude all other writes to the NSArray while it runs, but it also excludes all other reads, making the modification safe.

Example for concurrent queue should look like this:

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.
Community
  • 1
  • 1
Krzysztof Przygoda
  • 1,217
  • 1
  • 17
  • 24
3

If you want some samples of practical use look at this question of mine:

How do I resolve this deadlock that happen ocassionally?

I solve it by ensuring that my main managedObjectContext is created on the main thread. The process is very fast and I do not mind waiting. Not waiting means I will have to deal with a lot of concurency issue.

I need dispatch_sync because some code need to be done on main thread, which is the different thread than the one where to code is being executed.

So basically if you want the code to 1. Proceed like usual. You don't want to worry about race conditions. You want to ensure that the code is completed before moving on. 2. Done on a different thread

use dispatch_sync.

If 1 is violated, use dispatch_async. If 2 is violated just write the code like usual.

So far, I only do this once, namely when something need to be done on main thread.

So here's the 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]];
}
Community
  • 1
  • 1
user4951
  • 32,206
  • 53
  • 172
  • 282
2

dispatch_sync is mainly used inside dispatch_async block to perform some operations on main thread(like 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
  • 1,661
  • 14
  • 10
0

Here's a half-way realistic example. You have 2000 zip files that you want to analyze in parallel. But the zip library isn't thread-safe. Therefore, all work that touches the zip library goes into the unzipQueue queue. (The example is in Ruby, but all calls map directly to the C library. "apply", for example, maps to 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
  • 15,209
  • 4
  • 44
  • 64
-1

I've used dispatch sync when inside an async dispatch to signal UI changes back to the main thread.

My async block holds back only a little and I know the main thread is aware of the UI changes and will action them. Generally used this in a processing block of code that takes some CPU time but I still want to action UI changes from within that block. Actioning the UI changes in the async block is useless as UI, I believe, runs on the main thread. Also actioning them as secondary async blocks, or a self delegate, results in the UI only seeing them a few seconds later and it looks tardy.

Example block:

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 */});
         }

 });
Aardvark
  • 608
  • 5
  • 15