Threading is Dead. Long Live Threading!

by Ed on June 12, 2010

I stated in a past post that threads are evil. I still stand by that statement. Adding threads to an application has historically been fraught with issues. Many stem from people not quite understanding all of the situations can arise, etc. Oh sure, you might know what a deadlock is, but do you know all the circumstances they can occur? How does one safely shut a thread down? Did the owning object go away? It’s not as simple as it seems in most cases.

But what if I could thread until the break of dawn and not have to worry as much about those situations? That’s exactly what Grand Central Dispatch gives you on Mac OS X, and now iOS — access to concurrency without nearly as much pain.

This new world, however, does require you to think about problems a bit differently. I, at least, am so used to threading the old fashioned way that I had to figure out how to use dispatch_async and friends instead of my old cohorts.

One thing I will throw out there first is that threading with Obj-C/Foundation is so much easier than doing all the nasty work yourself. Methods like detachThreadSelector, et. al. go a long way towards making life simpler. NSOperation is also a big help. But GCD takes us beyond that with blocks (closures) and a full set of lower-level primitives that interact with Obj-C in ways that make sense and do most of the heavy lifting. But there are certainly some things you need to keep in mind, particular if you are using Obj-C.

Blocks

The first thing to learn about in this new world (and really, the thing that makes this all possible) is blocks. Blocks are essentially what other languages refer to as closures. All that ultimately matters is that it’s a way of designating a snippet of code to run. Think of them as fancy callbacks which can take any data they need with them (i.e. their context is omnipresent).

You specify a block with a special syntax. Ultimately, you enclose a block with ^{}:

^{ printf("this is a block!\n"); }

In the interest of keeping things simple, we’ll stop there and get onto some other basics.

Dispatch Queues

Typically, you’ll put blocks onto dispatch queues. There are two types of queues, concurrent, and FIFO. Regardless of the type of queue, you always know that blocks will be started in the order they are placed onto a particular dispatch queue. But in the concurrent case, they will be run on separate threads, whereas in the FIFO case you can run blocks one after the other, each one waiting for the previous one to finish first.

You can access a queue in three ways:

  1. call dispatch_get_global_queue(). This will give you a built-in concurrent queue. There are three, each at a different priority (low, medium, high).
  2. call dispatch_get_main_queue(). This queue represents a dispatch queue on the main thread. This is a FIFO queue, and tasks are run at ‘event time’ (i.e. inside the run loop).
  3. create your own queue with dispatch_queue_create(). This will always give you your very own FIFO queue.

Concurrent queues will create as many threads as needed to run the tasks. This doesn’t mean it will run as many threads as there are tasks, however, it is up to GCD to figure out how many threads make sense for the number of tasks which are attempting to run. So you might not always get 100% concurrency, as it might limit the number of threads to something smaller than the number of blocks waiting to run. But ultimately, to you the coder, it doesn’t matter. You just know that your tasks will be run at some point.

One thing to pay attention to is that concurrent queues are really concurrent. Just because they have the word ‘queue’ in their name doesn’t mean tasks run one after the other. It means they will start in a predictable order, but they will most often times be running at the same time. So only put tasks on concurrent queues that you can safely run in parallel. Many people seem to get bitten by this.

Queues vs. Threads

One of the things that confused me at first was that queues have nothing to do with threads you create. The only thread with a queue reserved for it is the main thread. I was so used to a life where each thread you create would get its own runloop and event queue (at least in Carbon) that I was thinking it might be the same here. But it is not (nor should it be). There is a function called dispatch_get_current_queue() (so really, there’s a fourth way to access a dispatch queue), but it is really for use inside a block that’s being executed. Calling it outside of a block will yield the default concurrent queue, which is generally not very meaningful.

What this means is that if you create a thread, that thread has nothing to do with GCD, and doesn’t tie into GCD at all. Of course, with GCD in the mix, if you create a thread, you might be doing something you don’t have to do. You can pretty much accomplish every thing you used to via threads with dispatch queues and blocks (and some other constructs we won’t discuss right now). Heck, you can even implement synchronization without locks. But that’s for another time.

Running Tasks

We’ve now talked about the two parts we need in order to finally run a task concurrently. In order to start an asynchronous task, you use dispatch_async(). Let’s say we wanted to run that block we used above. Here’s what we’d do:

dispatch_queue_t queue = dispatch_get_global_queue(
                                 DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

dispatch_async(queue, ^{
    printf("this is a block!\n");
});

That’s all there is to it. First we got access to one of the global concurrent queues, running a normal priority, then we called dispatch_async, specifying the queue and the block to run. It’s common to use the formatting you see above as it make reading the code a bit clearer.

I mentioned that blocks behave like closures and are run with their context intact. Let’s see that in action:

int i = 20;

dispatch_async(queue, ^{
    printf("The number was %d\n", i);
});

This will print:

The number was 20

So all variables in scope at the time the block was created are valid for the block’s execution later. In this case, we’re able to access i without issue.

So great, we ran a task. Yay for us, what if we need to know when it’s done? There are a couple of solutions, but let’s stick with the purity of what we’re working with: dispatch_async to the rescue!

dispatch_async(queue, ^{
    NSArray *results = ComputeBigKnarlyThingThatWouldBlockForAWhile();

    // tell the main thread
    dispatch_async(dispatch_get_main_queue(), ^{
        ProcessResults(results);
    });
});

In this case, after doing whatever long task you wished to make concurrent, we can then just use dispatch_async again to execute the ProcessResults function on the main thread. This is really the same thing as doing performSelectorOnMainThread:, but more flexible in that you aren’t restricted to what you can ‘pass’ to it.

Blocks in Objective-C

When used in Objective-C, block automatically retain any objects referenced within them. This is extremely helpful, as often times you might reference self, let’s say. This means your object won’t go away until that block has executed. So you’ll never run into situations where objects used inside the block have disappeared out from under you.

This isn’t true in C. If you were using CoreFoundation types in a C function, you’ll need to retain objects before calling dispatch_async and release them inside the block. For example:

void SaveArrayAsync(CFArrayRef array) {
    CFRetain(array);

    dispatch_queue_t queue = dispatch_get_global_queue(
                                 DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_async(queue, ^{
        SaveToDisk(array);
        CFRelease(array);
    }
}

But, as mentioned, in Objective-C, it’s not even something you need to think about.

- (void)saveArrayInBackground:(NSArray *)array {
    dispatch_queue_t queue = dispatch_get_global_queue(
                                 DISPATCH_QUEUE_PRIORITY_DEFAULT, 0);

    dispatch_async(queue, ^{
        [self saveToDisk:array];
    }
}

More to Come

We’ve covered just enough to start to be dangerous, but I think we’re at a good stopping point for now. I’ll be writing several more entries on using GCD and specifically how to take an old threaded concept and what you’d instead do with GCD. I’ll try to be regular about timing of these articles, so keep your eyes peeled.

{ 4 comments… read them below or add one }

Jesper June 13, 2010 at 7:20 am

dispatch_queue_t *queue is wrong. dispatch_queue_t is an opaque type and not an Objective-C object, so you don’t need the star. Otherwise, this is a good introduction.

Reply

Jesper June 13, 2010 at 7:21 am

Also, I hate Markdown’s intra-word-underline-means-emphasis rule, or rather its lack of an intra-word-exception.

Reply

Ed June 13, 2010 at 9:08 am

Indeed. Thanks for catching the errant *. I agree re: Markdown too. It was a pain to have to escape every single underscore.

Reply

Michael June 14, 2010 at 2:58 am

Thanks heaps, Ed, this was a great intro. Great to see how easy it makes asynchronous processing; maybe it’ll be enough to make more devs use it. Looking at you guys, iPhoto developers 😉

Reply

Leave a Comment

Previous post:

Next post: