Parallel

Mastering Threads on MacOS X

By José R.C. Cruz, March 07, 2012

The MacOS X platform has four thread solutions  two of which are obsolete as of version 10.7. Your choices are now POSIX threads or Cocoa's NSThread class. Here's how they compare and how to get up-to-speed on them quickly.

Working with NSThread

Assume the task routine is doFoo, defined by the class Foo. Assume also that the routine does not take any arguments. To create an NSThread instance within class Foo, use the factory method as follows:

The above statement refers to the doFoo routine with the @selector directive. The target class is set to self, the input argument to nil. The factory method returns the thread instance as its output, and it starts the thread.

Suppose the routine is doFooWithNumber:, which takes an integer value for input. In this case, enclose the value in an NSNumber object and pass the object to the factory method.

In its init method (line 26), Foo creates its own autorelease pool pPool. In its dealloc method, Foo disposes of the pool before calling its parent's dealloc (line 41). By using its own pool, Foo avoids using the main application process to dispose of its locally created objects. This improves thread performance and reduces the overall memory footprint.

Next, Foo defines two task routines: doFoo and doFooWithNumber:. The doFoo routine does not take any input arguments (lines 11, 48), while doFooWithNumber: takes an NSNumber argument (lines 12, 53). The doFooWithNumber: routine checks if its argument is a valid NSNumber object (lines 55-56). If that check proves false, doFooWithNumber: passes control to doFoo (line 61).

As for mutex constructs, Cocoa provides the NSLock class (Figure 2).

Figure 2: The NSLock class.

Unlike most Cocoa objects, an NSLock instance is created only with the standard alloc and init messages.

NSLock *tLock;
tLock = [[NSLock alloc] init];

This instance must not be marked for autorelease. It must be disposed of explicitly and only after all threads using the shared resource are done.

Listing Five describes how class Foo implements an NSLock as one of its properties.

In its init method, Foo creates its NSLock instance (pLock) right after creating autorelease pool (line 31). In its dealloc method, Foo disposes of the NSLock instance just before disposing of the autorelease pool (line 58). Its shared resource is the property modelBar, which is an instance of class Bar (line 11). Its creation and disposal are performed in the same init and dealloc methods (lines 34, 55).

The routine doFoo performs the first part of its threaded task. Before it access modelBar, doFoo sends a lock message to pLock (line 78). The routine reads its data from modelBar and sends an unlock message to pLock (line 80). Next, doFoo continues with the second part of its task. It sends another lock message to pLock (line 85) and writes its data to modelBar. It sends an unlock message (line 87) and does the rest of its task.

So when there are two NSThread instances, with both using doFoo, the pLock mutex prevents the two threads from using modelBar at the same time.

Comparing Thread Solutions

To compare performance, I ran a task of 32-bit Whetstone benchmark set for 5120 loops. It was run ten times, always from a cold start. The test machine was a MacBook with a 2.4 GHz Intel Core 2 Duo and 2 GB of physical RAM running MacOS X 10.6.6. On average, the POSIX solution ran the results in 7.5 seconds vs. 12.5 for NSThread.

In part this is because the NSThread instance is a heavyweight thread. It takes up more resources and it has a greater overhead. A POSIX thread lacks the rich feature set of NSThread. It is not object-oriented, due to its strict C-only interfaces. Plus, it uses explicit malloc() and free() calls to manage its memory. This means the thread cannot rely easily on garbage collection.

Instances of NSThread can communicate with each other in several ways. They can use an NSNotification object to post messages on the responder chain. Those messages are visible to other NSThread instances and to the main application process. An NSThread instance can use an NSMessagePort object to signal other active processes on the same machine. And it can use NSAppleEventDescriptor objects to control scriptable processes.

As for POSIX threads, they have to rely on a Mach port and a CFSocket. A Mach port is a lightweight user-level service provided by the Mach 3 kernel. CFSocket is a developer-friendly wrapper for BSD sockets. Both have C-style interfaces, and both require Cocoa objects rendered as CF data types.

Threading with Safety

Here are some guidelines for preparing a thread-safe task. These are taken from the official Apple guide on threaded programming. Though they focus on NSThread, they can be applied to POSIX threads as well.

Use immutable objects whenever possible.Immutable objects like NSString and CFString are thread-safe because their data content stays unchanged during a threaded task. When they modify their data, they return a modified copy as an immutable object. And when they extract parts of that same data, they return those parts as immutable objects as well. Mutable objects are not thread-safe. Their content can change unpredictably during the thread task. They also have a larger memory overhead than immutable ones.

Avoid sharing resources. A thread runs best when it maintains only its resources. If threads share a resource, they will need a mutual exclusion construct to co-ordinate their access. Too many mutexes, however, add to the application's memory footprint. They can also affect thread performance. If the shared resource is a view (like a progress bar), a thread should place its update code in the view's thread-safe routines. In the case of NSView, those routines would be lockFocusIfCanDraw and unlockFocus. Both routines run atomically, allowing the view to update its visual appearance.

Use mutex constructs properly and wisely. Use a mutex only to protect a shared resource. Create and dispose of it at the same time as the resource. Make sure no threads that use the construct are active and running before disposing of it. A thread should check for a valid mutex construct before attempting a lock. It should lock the mutex just before using the resource, and it should always clear the lock immediately afterwards. It should not use a locked resource as part of a loop or a recursion.

A thread should handle its own exceptions. A thread should avoid relying on the main application process to handle its exceptions. There is no guarantee the main process will handle a thread's exception properly. Plus, the context switch from thread to process degrades performance and leaves the thread unstable.

So, a thread should have its own exception traps. Those traps should catch every possible exceptions thrown by the thread. At least one trap should deal with all generic exceptions.

Conclusion

The MacOS X platform has four thread solutions, two of which are obsolete as of version 10.7. In this article, I explored the remaining solutions: the POSIX thread library and the NSThread class and compared the benefits of each as well as their performance in a single, quick benchmark. Threads can be beneficial to any Cocoa software product. When used properly, threads can help optimize performance. They allow the product make use of multicore processors, and they ensure the product remains responsive, even while running computationally intensive tasks. Choose the threading library that best suits your needs and you'll be ready to go.

José R.C. Cruz is a freelance engineering writer based in British Columbia. He frequently contributes articles to Dr. Dobb's and other technical publications. He can be contacted through at anarakisware@gmail.com.

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task.
However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

Video

This month's Dr. Dobb's Journal

This month,
Dr. Dobb's Journal is devoted to mobile programming. We introduce you to Apple's new Swift programming language, discuss the perils of being the third-most-popular mobile platform, revisit SQLite on Android
, and much more!