Channels ▼
RSS

C/C++

Mastering Threads on MacOS X


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:

NSThread *tFoo;
tFoo = [NSThread
	detachNewThreadSelectory:@selector(doFoo)
	toTarget:self withObject:nil];

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.

NSNumber *tArg;
tArg = [NSNumber numberWithInt:12345];
tFoo = [NSThread
	detachNewThreadSelectory:@selector(doFooWithNumber:)
	toTarget:self withObject:tArg];

The same also applies for other data types: strings with NSString, arrays with NSArray, and so on. Make sure to include the colon token in the routine's name as shown above.

To create a thread with the instance method, pair the method with an alloc call:

NSThread *tFoo;
tFoo = [[NSThread alloc] initWithTarget:self selector:@selector(doFoo)
	object:nil];

Note how the instance method uses a different order of arguments. Unlike the factory method, it one does not start the thread instance. To do so, send a start message to the instance:

[tFoo start];

As stated earlier, this method does not mark the thread for autorelease. To dispose of it, send a release message to the thread instance:

if ([tFoo isFinished])
	[tFoo release];

Make sure the thread is not running prior to disposal. Otherwise, the thread will fail with a BAD_EXC_ACCESS error.

Listing Four shows how we might prepare class Foo for threading.

Listing Four: Preparing a class for threading.

//
// CLASS:DECLARATION
//
#import <Cocoa/Cocoa.h>

@interface Foo : NSObject 
{
	NSAutoreleasePool *pPool;
}
// class methods go here
- (void)doFoo;
- (void)doFooWithNumber:(NSNumber *)anArg;
@end


//
// CLASS:IMPLEMENTATION
//
@implementation Foo
// Initialise the class
- (id)init
{
	if (self = [super init])
	{
		// create the local autorelease pool
		pPool = [[NSAutoreleasePool alloc] init];
		if (pPool != nil)
			return (self);
		else
			return (nil);
	}
	else
		// the parent has failed to initialise
		return (nil);
}

// Dispose the class
- (void)dealloc
{
	// dispose the pool
	[gPool release];
	
	// invoke the parent method
    [super dealloc];
}

// -- MAIN THREAD TASKS
- (void)doFoo
{
	// task code goes here...
}

- (void)doFooWithNumber:(NSNumber *)anArg
{
	if ((anArg != nil) 
		&& ([anArg isKindOfClass:[NSNumber class]])
	{
		// task code goes here...
	}
	else
		[self doFoo];
}
@end

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.

Listing Five: Implementing NSLock.

//
// CLASS:DECLARATION
//
#import <Cocoa/Cocoa.h>
@class Bar;

@interface Foo : NSObject 
{
	NSAutoreleasePool *pPool;
	NSLock *pLock;
	Bar *modelBar;
}
// instance methods go here
- (void)doFoo;
@end


//
// CLASS:IMPLEMENTATION
//
@implementation Foo
// Initialise the class
- (id)init
{
	if (self = [super init])
	{
		// create the local autorelease pool
		pPool = [[NSAutoreleasePool alloc] init];
		
		// create the mutex lock
		pLock = [[NSLock alloc] init];
		
		// create the shared object
		modelBar = [[Bar alloc] init];
		
		if (pPool != nil)
		{
			// more initialisation code follows...
			
			return (self);
		}
		else
			// unable to initialise the key properties
			return (nil);
	}
	else
		// the parent has failed to initialise
		return (nil);
}

// Dispose the class
- (void)dealloc
{		
	// dispose the shared object
	[modelBar release];
	
	// dispose the mutex lock
	[pLock release];
	
	// dispose the pool
	[pPool release];
	
	// invoke the parent method
    [super dealloc];
}

// -- MAIN THREAD TASKS
- (void)doFoo
{
	// check the key properties
	if ((gLock != nil) && (modelBar != nil))
	{
		id tData;
		
		// first part of task code runs...
		
		// lock the resource
		[gLock lock];
		tData = [modelBar getData];
		[gLock unlock];
		
		// second part of task code runs...
		
		// lock the resource
		[gLock lock];
		[modelBar setData:tData];
		[gLock unlock];
		
		// third part of task code runs...
	}
}
@end

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.

References

NSThread Class Reference. MacOS X Developer Library [PDF].

pthread(3) MacOS X Manual Page. MacOS X Developer Library. [HTML].

Threading Programming Guide. MacOS X Developer Library. [PDF].

Blaise Barney. POSIX Threads Programming. Lawrence Livermore National Laboratory. UCRL-MI-133316.

Wikipedia. POSIX Threads.


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.


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

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.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.
 

Video