Thread management

Each process (application) in OS X or iOS consists of one or more threads, each representing a single path of execution through the application code. Each application starts with a thread that runs the main functions of the application. An application can generate other threads, each executing code for a particular function.

When an application generates a new thread, that thread becomes a separate entity within the application process space. Each thread has its own execution stack, and the runtime is scheduled separately by the kernel. Threads can communicate with other threads and other processes, perform I/O operations, and do whatever you might need to do. However, because they are in the same process space, all threads in a single application share the same virtual memory space and have the same access rights as the process itself.

This chapter provides an overview of the threading technologies available in OS X and iOS, as well as examples of how to use them in your applications.

Note: For a history of Mac OS threading architecture and additional background information about threads, see technical note TN2028, “Threading Architecture.”

Cost of thread

Threads are a real cost to programs (and systems) in terms of memory usage and performance. Each thread needs to allocate memory between the kernel memory space and the program memory space. The core structure needed to manage threads and coordinate their scheduling is stored in the kernel using wired memory. The thread stack space and per-thread data are stored in the program’s memory space. Most of these structures are created and initialized when you first create a thread – a process that can be relatively expensive due to the required interaction with the kernel.

Table 2-1 quantifies the approximate costs associated with creating new user-level threads in your application. Some of these costs are configurable, such as the amount of stack space allocated to worker threads. The time cost of creating a thread is a rough approximation and should only be used for comparison. Depending on processor load, computer speed, and the amount of system and program memory available, thread creation times can vary widely.

Table 2-1: Thread creation costs

project The approximate cost notes
Kernel data structure About 1 KB Most of this memory, which is used to store thread data structures and properties, is allocated as wired memory and therefore cannot be paged to disk.
Stack space 512 KB (secondary thread) 8 MB (OS X main thread) 1 MB (iOS main thread) The minimum stack size allowed by a worker thread is 16 KB, and the stack size must be a multiple of 4 KB. When you create a thread, you leave room for this memory in your process space, but the actual page associated with that memory is not created until it is needed.
Initialization time About 90 microseconds This value reflects the time between the initial call to create the thread and the time when the thread’s entry point routine begins execution. These figures were determined by analyzing the average and median generated when threads were created on an Intel-based iMac using a 2 GHz Core Duo processor and 1 GB of RAM running OS X V10.5.

Note: Because of their underlying kernel support, operation objects can often create threads faster. Instead of creating threads from scratch each time, they use thread pools that already reside in the kernel to save allocation time. For more information about working with action objects, see Concurrency Programming Guide.

Another cost to consider when writing threaded code is production costs. Designing threaded applications may sometimes require radical changes in the way the application’s data structure is organized. These changes may be necessary to avoid using synchronization, which in itself can cause a significant performance penalty for poorly designed applications. Designing these data structures and debugging problems in threaded code increases the time required to develop threaded applications. However, if your thread is spending too much time waiting for locks or doing nothing, avoiding these costs can cause bigger problems at run time.

Create a thread

Creating the underlying thread is relatively simple. In all cases, you must have a function or method that acts as the main entry point for the thread, and you must use an available thread routine to start the thread. The following sections show the basic creation process for common threading techniques. Threads created using these techniques inherit a set of default properties, which are determined by the technology you use. For information on how to configure threads, see Configuring Thread Properties.

Using NSThread

There are two ways to create a thread using the NSThread class:

  • usedetachNewThreadSelector:toTarget:withObject:The class method generates a new thread.
  • Create a new oneNSThreadObject and call itstartMethods. (Supported only on iOS and OS X V10.5 and later.)

Because every version of OS X support detachNewThreadSelector: toTarget: withObject: method, so often be found in the existing Cocoa applications using a thread it. To detangle a new thread, simply provide the name of the method to be used as the thread entry point (specified as a selector), the object that defines the method, and any data to be passed to the thread at startup. The following example shows a basic call to this method, which uses a custom method of the current object to generate a thread.

NSThread detachNewThreadSelector:@selector(myThreadMainMethod:) toTarget:self withObject:nil];
Copy the code

Prior to OS X V10.5, you primarily used the NSThread class to generate threads. While you can get NSThread objects and access certain thread properties, you can only do this from the thread itself after it has run. In OS X V10.5, support was added for creating NSThread objects without the corresponding new threads being generated immediately. (This support is also available on iOS.) This support makes it possible to get and set various thread properties before starting a thread. It also makes it possible to use the thread object later to reference the running thread.

In OS X v10.5 and higher in the initialization NSThread object is the simple way to use initWithTarget: selector: object: method. This method is to obtain and detachNewThreadSelector: toTarget: withObject: exactly the same information, and use it to initialize new NSThread instance. However, it does not start the thread. To start the thread, explicitly call the start method on the thread object, as shown in the following example:

NSThread* myThread = [[NSThread alloc] initWithTarget:self selector:@selector(myThreadMainMethod:) object:nil];
[myThread start];  // Actually create the thread
Copy the code

Note: use initWithTarget: selector: object: method of the alternative is to subclass NSThread and cover the main method. You will use a rewrite version of this method to implement the main entry point of the thread. For more information, see the subclass comments in the NSThread Class Reference.

If you have an NSThread object is currently running thread, you can send the message to the thread of a kind of method is to use the performSelector: onThread: withObject: waitUntilDone: almost any object in the application of the method. Support for executing selectors on threads (except the main thread) was introduced in OS X V10.5, which is a convenient way to communicate between threads. (This support is also available on iOS.) Messages sent using this technique are executed directly by other threads as part of their normal running loop processing. (Of course, this does mean that the target thread must run in its run loop; See Running loops.) When you communicate this way, you may still need some form of synchronization, but it’s simpler than setting up communication ports between threads.

Note: although occasionally communicate between threads is very good, but should not use the performSelector: onThread: withObject: waitUntilDone: method to handle emergency communication between threads or frequent communication with this technique.

For a list of other thread communication options, see Setting thread separation Status.

Use POSIX threads

OS X and iOS provide C-based support for creating threads using POSIX threads apis. This technique can be used with virtually any type of application (including Cocoa and Cocoa Touch applications), and may be more convenient if you write software for multiple platforms. The POSIX routine used to create the thread is sufficient to call pthread_CREATE appropriately.

Table 2-1 shows two custom functions that create threads using POSIX calls. The LaunchThread function creates a new thread, and the main program is implemented in the PosixThreadMainRoutine function. Because POSIX creates threads as connectable by default, this example changes the attributes of the thread to create detached threads. Marking a thread as detached gives the system an opportunity to reclaim the thread’s resources immediately upon exit.

Table 2-1: Create a thread using C

#include <assert.h>
#include <pthread.h>
 
void* PosixThreadMainRoutine(void* data)
{
    // Do some work here.
 
    return NULL;
}
 
void LaunchThread(a)
{
    // Create the thread using POSIX routines.
    pthread_attr_t  attr;
    pthread_t       posixThreadID;
    intreturnVal; returnVal = pthread_attr_init(&attr); assert(! returnVal); returnVal = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); assert(! returnVal);int     threadError = pthread_create(&posixThreadID, &attr, &PosixThreadMainRoutine, NULL); returnVal = pthread_attr_destroy(&attr); assert(! returnVal);if(threadError ! =0)
    {
         // Report an error.}}Copy the code

If you add the code from the previous list to one of the source files and call the LaunchThread function, it will create a new detached thread in your application. Of course, a new thread created with this code doesn’t do anything useful. The thread will start and exit almost immediately. To make things more interesting, you need to add code to the PosixThreadMainRoutine function to do some real work. To ensure that the thread knows what to do, you can pass it Pointers to some data at creation time. You pass this pointer as the last argument to the pthread_create function.

To pass information from the newly created thread back to the main thread of your application, you need to establish communication paths between the target threads. For C-based applications, there are several ways to communicate between threads, including using ports, conditions, or shared memory. For long-running threads, you should almost always set up some sort of interthread communication mechanism to provide the main thread of your application with a way to check the thread state, or to clean it up when the application exits.

For more information about POSIX thread functions, see the PThread man page.

Generate a thread with NSObject

In iOS and OS X V10.5 and later, all objects can generate a new thread and use it to execute one of the methods. PerformSelectorInBackground: withObject: separation method to create a new thread, and using the specified method as the entry point for the new thread. For example, if you have an object (represented by the variable myObj) and that object has a doSomething method that you want to run in a background thread, you can do this using the following code:

[myObj performSelectorInBackground:@selector(doSomething) withObject:nil];
Copy the code

The effect of this method is called and called detachNewThreadSelector: toTarget: withObject: NSThread method (the current object, selector and parameter object as a parameter) is the same. Create a new thread immediately and start running using the default configuration. Inside the selector, you must configure the thread as you would any thread. For example, if you plan to use it, you need to set up the automatic release pool (if you are not using garbage collection) and configure the running loop for the thread. For information on how to configure a new thread, see Configuring Thread Properties.

Use POSIX threads in Cocoa applications

Although the NSThread class is the primary interface for creating threads in Cocoa applications, you are free to use POSIX threads if it is more convenient for you to do so. For example, if you already have code that uses them and you don’t want to rewrite it, POSIX threads can be used. If you plan to use POSIX threads in Cocoa applications, you should still be aware of the interaction between Cocoa and threads and follow the guidelines in the following sections.

Securing the Cocoa framework

For multithreaded applications, the Cocoa framework uses locking and other forms of internal synchronization to ensure that they behave correctly. However, to prevent these locks from degrading performance in the single-threaded case, Cocoa does not create them until the application generates its first new thread using the NSThread class. If only POSIX threading routines were used to generate threads, Cocoa will not receive notification that it needs to know that your application is now multithreaded. When this happens, actions involving the Cocoa framework can make your application unstable or crash.

To let Cocoa know that you intend to use multiple threads, all you have to do is generate a thread using the NSThread class and let it exit immediately. Your thread entry point doesn’t need to do anything. The mere act of generating threads using NSThreads is enough to ensure that the locks required by the Cocoa framework are in place.

If you are not sure whether Cocoa considers your application to be threaded, you can check using the isMultiThreaded method of NSThread.

Mix POSIX and Cocoa locks

It is safe to use a mixture of POSIX and Cocoa locks in the same application. Cocoa locks and condition objects are basically just wrappers for POSIX mutexes and conditions. However, for a given lock, the same interface must always be used to create and manipulate the lock. In other words, you cannot use Cocoa NSLock objects to manipulate mutex created using the pthread_mutex_init function, and vice versa.

Configuring thread properties

After creating a thread, and sometimes before, you may want to configure different parts of the threading environment. The following sections describe some of the changes you can make and when you can make them.

Configure the stack size of the thread

For each new thread you create, the system allocates a specific amount of memory in the process space to act as a stack for that thread. The stack manages the stack frame, which is also where any local variables of the thread are declared. The amount of memory allocated to a thread is listed in the thread cost.

If you want to change the stack size for a given thread, you must do this before creating the thread. Although setting the stack size using NSThread is only available in iOS and OS X V10.5 and later, all threading technologies provide some means of setting the stack size. Table 2-2 lists the different options for each technique.

Table 2-2: Setting stack sizes for threads

technology options
Cocoa On iOS and OS X V10.5 and later, allocate and initializeNSThreadObject (do not usedetachNewThreadSelector:toTarget:withObject:Methods). In the calling thread objectstartBefore using thesetStackSize:Method specifies the new stack size.
POSIX Create a new onepthread_attr_tStructure and usepthread_attr_setstacksizeFunction to change the default stack size. When the thread is created, the property is passed topthread_createFunction.
Multiprocessing service When the thread is created, pass the appropriate stack size value toMPCreateTaskFunction.

Configure thread local storage

Cocoa and POSIX store thread dictionaries differently, so you can’t mix and match calls to the two technologies. However, as long as you stick to one technique in threaded code, the end result should be similar. In Cocoa, you use the threadDictionary method of the NSThread object to retrieve the NSMutableDictionary object, to which you can add any key needed by the thread. In POSIX, you use the pthread_setSpecific and pthread_getSpecific functions to set and get the keys and values of a thread.

Sets the thread separation state

Most advanced threading technologies create detached threads by default. In most cases, threads are preferred because they allow the system to release the thread’s data structure as soon as it completes. Separate threads also do not require explicit interaction with your program. The method of retrieving results from the thread is up to you. In contrast, the system does not reclaim the resources of a connectable thread until another thread explicitly joins the thread, and this process may block the thread performing the connection.

You can think of a connectable thread as similar to a child thread. Although they still run as separate threads, the connectable threads must be connected by another thread before the system can reclaim their resources. Connectable threads also provide an explicit way to pass data from an existing thread to another thread. Before it exits, a joinable thread can pass a data pointer or other return value to the pthread_exit function. Another thread can then declare this data by calling the pthread_join function.

Important: Detached threads can terminate immediately upon application exit, but joinable threads cannot. Each connectable thread must be connected before the process is allowed to exit. Therefore, a connectable thread may be preferred in cases where the thread is performing critical work that should not be interrupted, such as saving data to disk.

If you really want to create joinable threads, the only way is to use POSIX threads. By default, POSIX creates threads to be connectable. To mark a thread as detached or connectable, modify the thread properties using the pthread_attr_setdetachState function before creating the thread. Once the thread is started, you can change the joinable thread to a detached thread by calling the pthread_detach function. For more information about these POSIX thread functions, see the PThread man page. For information on how to join threads, see the pthread_JOIN man page.

Write thread entry routines

In most cases, thread entry point routines are structured the same in OS X as they are on other platforms. Initialize the data structure, perform some operations or optionally set up a run loop, and clean up when the threaded code completes. Depending on your design, you may need to perform some additional steps when writing your introductory routine.

Create an automatic release pool

Applications linked in the objectivec framework must typically create at least one automatic release pool per thread. If the application uses the managed model – where the application handles reserved and freed objects – the automatic release pool captures any objects that are automatically released from that thread. If your application uses garbage collection instead of a managed memory model, it is not necessary to create an automatic release pool. The presence of auto-release pools in garbage collection applications is harmless and largely ignored. Allows cases where code modules must support both garbage collection and managed memory models. In this case, an automatic release pool must exist to support managed memory model code that will be ignored if the application is running with garbage collection enabled. If your application uses a managed memory model, creating an automatic release pool should be the first thing you do in your thread entry routine. Also, destroying the auto-release pool should be the last thing you do in your thread. This pool ensures that automatically freed objects are captured, but not released until the thread itself exits. Table 2-2 shows the structure of a basic thread entry routine that uses automatic release pooling.

Table 2-2: Define your thread entry routines

- (void)myThreadMainRoutine
{
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; // Top-level pool
 
    // Do thread work here.
 
    [pool release];  // Release the objects in the pool.
Copy the code

Because the top-level auto-release pool does not release its objects until the thread exits, long-running threads should create additional auto-release pools to release objects more frequently. For example, a thread using a run loop might create and release an automatic release pool each time through the run loop. Releasing objects more frequently prevents your application from becoming too memory intensive and causing performance problems. As with any performance-related behavior, you should measure the actual performance of your code and adjust the use of auto-release pools appropriately.

For more information about Memory Management and automatic release pooling, see Advanced Memory Management Programming Guide.

Set up the exception handler

If your application catches and handles exceptions, you should have threaded code ready to catch any exceptions that might occur. Although it is best to handle exceptions as they occur, catching exceptions in a thread can cause the application to exit. Installing the final try/catch in the thread entry routine allows you to catch any unknown exceptions and provide the appropriate response.

When building projects in Xcode, you can use either the C ++ or objectivec exception-handling styles. For information on setting up how to raise and catch exceptions in objectivec, see Exception Programming Topics.

Set the run cycle

When writing code to run on a separate thread, you have two choices. The first option is to write the thread’s code as a long task, execute it with little or no interruption, and let the thread exit when it is finished. The second option is to place your thread in a loop and have it process the request dynamically when it arrives. The first option requires no special Settings for your code; You just started the job you wanted to do. The second option, however, involves setting the running loop for the thread.

OS X and iOS provide built-in support for running loops in each thread. The application framework automatically starts the application main thread running loop. If you create any worker threads, you must configure the run loop and start it manually.

For information about using and configuring run loops, see Run Loops.

Termination of the thread

The recommended way to exit a thread is to let it exit its entry point routine normally. Although Cocoa, POSIX, and multiprocessor services provide routines that directly kill threads, they are strongly discouraged. Killing a thread prevents the thread from cleaning itself. Memory allocated by a thread can be leaked, and any other resources currently being used by the thread may not be cleaned up properly, potentially causing problems later.

If you expect to terminate a thread during an operation, you should design the thread from the beginning to respond to cancel or exit messages. For long-running operations, this might mean periodically stopping work and checking for such messages to arrive. If a message does ask the thread to exit, the thread will have the opportunity to perform any required cleanup and exit normally. Otherwise, it can simply return to work and process the next block of data.

One way to respond to cancellation messages is to use a run-loop input source to receive such messages. Table 2-3 shows how this code looks in the main entry routine of a thread. (This example shows only the main loop section and does not include steps to set up the auto-release pool or configure the actual work to be performed.) This example installs custom input sources on the run loop, possibly from another of your threads; For information about setting up input sources, see Configuring Running Loop Sources. After executing a portion of the total workload, the thread briefly runs a run loop to see if any messages have arrived at the input source. If not, the run loop immediately exits and the loop continues to the next working block. Because the handler does not have direct access to the exitNow local variable, exit conditions are passed through key-value pairs in the thread dictionary.

Table 2-3: Check exit conditions during long hours

- (void)threadMainRoutine
{
    BOOL moreWorkToDo = YES;
    BOOL exitNow = NO;
    NSRunLoop* runLoop = [NSRunLoop currentRunLoop];
 
    // Add the exitNow BOOL to the thread dictionary.
    NSMutableDictionary* threadDict = [[NSThread currentThread] threadDictionary];
    [threadDict setValue:[NSNumber numberWithBool:exitNow] forKey:@"ThreadShouldExitNow"];
 
    // Install an input source.
    [self myInstallCustomInputSource];
 
    while(moreWorkToDo && ! exitNow) {// Do one chunk of a larger body of work here.
        // Change the value of the moreWorkToDo Boolean when done.
 
        // Run the run loop but timeout immediately if the input source isn't waiting to fire.
        [runLoop runUntilDate:[NSDate date]];
 
        // Check to see if an input source handler changed the exitNow value.
        exitNow = [[threadDict valueForKey:@"ThreadShouldExitNow"] boolValue]; }}Copy the code