It has long been a standard programming technique to optimize program performance through multithreading. By having data processing and I/O operations run on their own secondary threads and dedicating the main thread to user-interface management, you can enhance the responsiveness of your applications. But in recent years, multithreading has assumed even greater importance with the emergence of multicore computers and symmetric multiprocessing (SMP). Concurrent processing on such systems involves synchronizing the execution of not only multiple threads on the same processor but multiple threads on multiple processors.
But making your program multithreaded is not without risk or costs. Although threads share the virtual address space of their process, they still add to the process’s memory footprint; each thread requires an additional data structure, associated attributes, and its own stack space. Multithreading also requires greater complexity of program design to synchronize access to shared memory among multiple threads. Such designs make a program harder to maintain and debug. Moreover, multithreading designs that overuse locks can actually degrade performance (relative to a single-threaded program) because of the high contention for shared resources. Designing a program for multithreading often involves tradeoffs between performance and protection and requires careful consideration of your data structures and the intended usage pattern for extra threads. Indeed, in some situations the best approach is to avoid multithreading altogether and keep all program execution on the main thread.
Further Reading: To get a solid understanding of issues and guidelines related to multithreading, read Threading Programming Guide. This document contains several articles specific to Cocoa multithreading, including “Cocoa Thread Safety,“ “Creating Threads in Cocoa,“ and “Using Locks in Cocoa.“
Multithreading and Multiprocessing Resources
Multithreading Guidelines for Cocoa Programs
Are the Cocoa Frameworks Thread Safe?
Cocoa provides several classes that are useful for multithreading programs.
In Cocoa threads are represented by objects of the NSThread class. The implementation of the class is based upon POSIX threads. Although the interface of NSThread does not provide as many options for thread management as do POSIX threads, it is sufficient for most multithreading needs.
Run loops are a key component of the Mac OS X architecture for the distribution of events. Run loops have input sources (typically ports or sockets) and timers; they also have input modes for specifying which input sources the run loop listens to. Objects of the NSRunLoop class represent run loops in Cocoa. Each thread has its own run loop, and the run loop of the main thread is set up and run automatically when a process begins execution. Secondary threads, however, must run their own run loops.
With the NSOperation and NSOperationQueue classes you can manage the execution of one or more encapsulated tasks that may be concurrent or nonconcurrent. (Note that the word “task” here does not necessarily imply an NSTask object; a task, or operation, for example, typically runs on a secondary thread within the same process.) An NSOperation object represents a discrete task that can be executed only once. Generally, you use NSOperationQueue objects to schedule operations in a sequence determined by priority and interoperation dependency. An operation object that has dependencies does not execute until all of its dependent operation objects finish executing. An operation object remains in a queue until it is explicitly removed or it finishes executing.
Note: The NSOperation and NSOperationQueue classes were introduced in Mac OS X v10.5. They are not available in earlier versions of the operating system.
Locks act as resource sentinels for contending threads; they prevent threads from simultaneously accessing a shared resource. Several classes in Cocoa provide lock objects of different types.
NSConditionLock and NSCondition are similar in purpose and implementation; both are object-oriented implementations of pthread condition locks. The implementation of NSConditionLock is more thorough but less flexible (although it does offer features such as locking timeouts); it implements both the semphore signaling and the mutex locking for you. The implementation of NSCondition, on the other hand, wraps the pthread condition variables and mutex locks and requires you to do your own locking, signaling, and predicate state management. However, it is more flexible. This approach closely follows the implementation pattern for pthread conditions.
Note: The NSCondition class was introduced in Mac OS X v10.5. It is not available in earlier versions of the operating system.
When your program has multiple threads, those threads often need a way to communicate with each other. As you might have read in the preceding section, the condition-lock classes use pthread-based condition signaling (semaphores) as a way to communicate between threads. But Cocoa offers several other resources for interthread communication.
Objects of the NSMachPort and NSMessagePort classes enable interthread communication over Mach ports. Objects of the NSSocketPort class enable interthread and interprocess communication over BSD sockets. You can also use distributed objects as a mechanism for interthread and interprocess communication; for more on this port-based communication abstraction, see Distributed Objects Programming Topics.
In addition, the NSObject class provides the performSelectorOnMainThread:withObject:waitUntilDone: method which allows secondary threads to communicate with and send data to the main thread.
Whetherf your program uses the Cocoa frameworks or something else, there are some common observations that can be made about the appropriateness and wisdom of having multiple threads in your program. You might start by asking yourself the following questions; if you answer “yes” to any of them, you should consider multithreading:
Is there a CPU- or I/O-intensive task that could block your application’s user interface?
The main thread manages an application’s user interface—the view part of the Model-View-Controller pattern. Thus the application can perform work that involves the application’s data model on one or more secondary threads. In a Cocoa application, because controller objects are directly involved in the management of the user interface, you would run them on the main thread.
Does your program have multiple sources of input or multiple outputs, and is it feasible to handle them simultaneously?
Do you want to spread the work your program performs across multiple CPU cores?
Let’s say one of these situations applies to your application. As noted earlier, multithreading can involve costs and risks as well as benefits, so it’s worthwhile to consider alternatives to multithreading. For example, you might try asynchronous processing.
Just as there are situations that are appropriate for multithreading, there are situations where it’s not:
The work requires little processing time.
The work requires steps that must be performed serially.
Underlying subsystems are not thread safe.
The last item brings up the important issue of thread safety. There are some guidelines you should observe to ensure—as much as possible—that your multithreaded code is thread safe. Some of these guidelines are general and others are specific to Cocoa applications. The general guidelines are as follows:
Avoid sharing data structures across threads if possible.
Instead you can give each thread its own copy of an object (or other data) and then synchronize changes using a transaction-based model. Ideally you want to minimize resource contention as much as possible.
Lock data at the right level.
Locks are an essential component of multithreaded code, but they do impose a performance bottleneck and may not work as planned if used at the wrong levvel.
Catch local exceptions in your thread.
Each thread has its own call stack and is therefore responsible for catching any local exceptions and cleaning things up. The exception cannot be thrown to the main thread or any other thread for handling. Failure to catch and handle an exception may result in the termination of the thread or the owning process.
Avoid volatle variables in protected code.
The additional guidelines for Cocoa programs are few:
Deal with immutable objects in your code as much as possible, especially across interface boundaries.
Objects whose value or content cannot be modified—immutable objects—are usually thread safe. Mutable objects are often not. As a corollary to this guideline, respect the declared mutability status of objects returned from method calls; if you receive an object declared as immutable but it is mutable and you modify it, you can cause behavior that is catastrophic to the program.
The main thread of a Cocoa application is responsible for receiving and dispatching events. Although your application can handle events on a secondary thread instead of the main thread, if it does it must keep all event-handling code on that thread. If you spin off the handling of events to different threads, user events (such as the letters typed on the keyboard) can occur out of sequence.
If a secondary thread is to draw a view, ensure that all drawing code falls between calls to the NSView methods lockFocusIfCanDraw and unlockFocus.
Note: You can learn more about these guidelines in the “Thread Safety Guidelines“ and “Cocoa Thread Safety“ articles of Threading Programming Guide.
Before you can make your program thread safe, it’s essential to know the thread safety of the frameworks that your program relies on. The core Cocoa frameworks, Foundation and Application Kit, have parts that are thread safe and parts that are not.
Generally, the classes of Foundation whose objects are immutable collections (NSArray, NSDictionary, and so on) as well as those whose immutable objects encapsulate primitive values or constructs (for example, NSString, NSNumber, NSException) are thread safe. Conversely, objects of the mutable versions of these collection and primitive-value classes are not thread safe. A number of classes whose objects represent system-level entities—NSTask, NSRunLoop, NSPipe, NSHost, and NSPort, among others—are not thread safe. (NSThread and the lock classes, on the other hand, are thread safe.)
In the Application Kit, windows (NSWindow objects) are generally thread safe in that you can create and manage them on a secondary thread. Events (NSEvent objects), however, are safely handled only on the same thread, whether that be the main thread or a secondary thread; otherwise your run the risk of having events get out of sequence. Drawing and views (NSView objects) are generally thread safe, although operations on views such as creating, resizing, moving should happen on the main thread.
For more information on the thread safety of the Cocoa frameworks, see “Cocoa Thread Safety“ in Threading Programming Guide.
Last updated: 2007-10-31