Grand Central Dispatch: Exciting or Overhyped?
Since Apple announced OS X 10.6, one feature has had an incredibly high hype-to-details ratio in all of its marketing material. Grand Central Dispatch (GCD), we were told, would revolutionize how developers think about multithreading and make it trivial for developers to write concurrent code.
Since then, OS X 10.6 was released and we all got a good look at how GCD could be used. Then, Apple announced that libdispatch, the core of GCD, was going to be open-sourced and we also got to see how it worked internally. In this article, I'm going to take a look at exactly what GCD is and see whether it lives up to the hype.
Building Blocks
The existence of closures is often seen as one of the features that differentiates a high-level language, like Lisp or Smalltalk, from a low-level language like C. A new generation of programmers growing up with languages like Ruby (a dialect of Smalltalk written by people who thought Perl syntax was a good idea) has lead to more people seeing what some have been claiming since 1958: Closures are incredibly useful. This has led to a rapid attempt to shoehorn them back into existing, lower-level, languages. The next version of the C++ standard, for example, now includes closures.
With 10.6, Apple introduced closures in C, which they called blocks, after the Smalltalk term for the same feature. These are a proprietary Apple extension, but that doesn't mean that they are limited to OS X. Like Objective-C, they require support from the compiler and a small runtime library. Apple's branch of GCC supports blocks and so does clang, the new C-family front end for LLVM. There are several versions of the runtime library. Apple shipped one with OS X 10.6, and there is an open-source version of this in LLVM's compiler-rt library, although this contains several Darwinisms and so doesn't yet work on other platforms. Remy Demarest wrote a replacement a few months ago, and we've been shipping a modified version of that with É[ae]toile[ae] for several months; it has now been incorporated into GNUstep's Objective-C runtime.
Blocks, therefore, can be used almost anywhere. OS X 10.6 provides a number of new places where they can be used. All the Cocoa collection classes, for example, support enumeration by blocks, but their penetration goes a lot deeper, right down into the C standard library. Most C developers will be familiar with functions like qsort(), the C standard library implementation of a Quicksort, which takes a function pointer as an argument for comparing elements in two arrays. Because OS X's libc is based on the FreeBSD implementation, it also includes similar functions that implement mergesort and heapsort. With 10.6, each of these now has a _b variant, for example qsort_b(). These are identical to the original versions, but take a block instead of a function pointer as the comparison function.
So how do blocks work? When you create a block, you create a function inside another function, like this:
int (^compare)(void*,void*) = ^(void *a, void *b) { // Do some calculation return comparison_result; };
The compare variable now contains a pointer to a block. You can use it like a function pointer, calling it just as you would a function, but there are subtle differences. The code inside the block can refer to any variables valid inside the scope where the block was declared. By default, the block will only get a copy of these variables, but if they are declared with the __block modifier, then the block and the enclosing scope will refer to the same copy:
__block int a = 1; void (^aBlock)(void) = ^{ a++; }; aBlock(); aBlock(); // Now a == 3;
Although the lines aBlock() look like normal function invocations, they are not. When you call a normal function, or a function pointer, the function name is a variable containing the address of the function. The compiler will push the arguments onto the stack (or into registers) and then jump to this address. Blocks works slightly differently. The block pointer is a pointer to a structure. This contains, among other things, a pointer to a function. When you call the block, you are really calling this function, with the block as a hidden first argument.
The existence of the hidden argument is what allows the block function to refer to external variables. Other fields in this structure contain pointers to the other variables. The implementation details are a bit more complicated than this. For example, multiple blocks can refer to the same on-stack variable, and the blocks can persist longer than the enclosing scope. This requires the variable to be copied off the stack and into a reference-counted structure. Objective-C and C++ objects also need to have destructors run when the final block referencing them is destroyed. Fortunately, the blocks runtime hides all these messy details from most programmers, just as the Objective-C runtime hides the messy details of message sending from programmers.