Threads

Modified

Downloads

iPhone timer

iPhone threaded UI

iPhone unthreaded UI

iPhone Echo Client using NSStream and NSRunLoop

iPhone Threaded XML Parser with Delegation

Resources

Apple's Threading OS X and iPhone documentation

Multithreading in Cocoa an overview

Producer/Consumer example

Thread-safety

Overview

Threads are useful when some operation, such as downloading a file, effectively blocks other execution, such as user interaction.

All applications have a primary or main thread, in which the UI events are managed. Additional threads can be started, that operate in synchronous or asynchronous execution.

Unlike Java, which provides a simple threading mechanism, OS X and iPhone offer many threading options, each appropriate for specific applications. We will examine the main classes.

NSTimer            iPhone timer

Timers run in the thread from which they are scheduled. Timers are not a thread but can provide asynchronous execution behavior.

In the following example, a timer decrements a counter each second from 10 to 1 before terminating the timer. The following starts the timer, which runs at one second intervals.

myTimer =
          [NSTimer scheduledTimerWithTimeInterval: 1
                         target:self selector:@selector(countDown)
                         userInfo:nil
                         repeats:YES];
countLabel.text = @"Done";

Here, the current thread starts the timer and continues execution, the timer firing at one second intervals.

To illustrate, the count label first displays @"Done" from the current thread, the 10 to 1 due to timer firing.

#import <UIKit/UIKit.h>
 
@interface iPhoneCountdownViewController : UIViewController {
    NSTimer *myTimer;
    int     count;
    IBOutlet UILabel *countLabel;
}
@end

#import "iPhoneCountdownViewController.h"
 
@implementation iPhoneCountdownViewController
 
-(void) countDown {
    NSString* buf=[[NSString alloc] initWithFormat:@"%d", count];
    count--;
    countLabel.text = buf;
    [buf release];
    if(count == 0) [myTimer invalidate];
}
 
- (void)viewDidLoad {
      [super viewDidLoad];
   
      count = 10;

myTimer =
          [NSTimer scheduledTimerWithTimeInterval: 1
                         target:self selector:@selector(countDown)
                         userInfo:nil
                         repeats:YES];

      countLabel.text = @"Done";
}
 
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}
- (void)viewDidUnload {}
- (void)dealloc {
    [super dealloc];
}
@end

NSRunLoop

The thread execution at right illustrates how one thread services multiple events in a run loop.

NSRunLoop class adds and removes service events on an existing thread.

In general, your application does not need to explicitly manage NSRunLoop objects. Each NSThread object, including the application’s main thread, has an NSRunLoop object automatically created for it as needed. It is not possible to create a separate NSRunLoop object; to access the current thread’s run loop, do so with the class method currentRunLoop.

NSTimer objects are added to the current thread run loop or can be added to others by NSRunLoop.

Communication ports which need to service events asynchronously, that is without blocking the current thread, can be added to a run loop. The port is then handled within the thread's run loop without blocking other services in the same thread.

The following schedules inputStream, which reads data from a port, on the currentRunLoop.

[inputStream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];

For a discussion, see NSStream.

For a complete example using NSStream and NSRunLoop, download iPhone Echo Client using NSStream and NSRunLoop.

NSThread

The following example has two threads, the primary thread of all executing applications, and a second child thread created in MyThread class.

When the primary thread terminates, any child threads are automatically terminated. The following blocks the primary thread by requesting user input.

The two threads execute in parallel, each yielding execution to other waiting threads by:

[NSThread sleepForTimeInterval: 0.001];

The second tread is started by:

  1. NSThread *myThread =  [[NSThread alloc] initWithTarget:self selector:@selector(run:) object:nil];
  2. [myThread start];                                

Line 1:

Line 2:

The second thread -run: method executes a for statement.

 

#import <Foundation/Foundation.h>
 
@interface MyThread : NSObject {}
-(void) startThread;
-(void) run: (id) param;
-(void) print: (char) ch;
@end
 
@implementation MyThread
- (void) startThread {
    NSThread* myThread = [[NSThread alloc] initWithTarget:self
                                                 selector:@selector(run:)
                                                 object: nil];
    [myThread start];           // Call -run: on thread
}
 
-(void) print: (char) ch {
    printf("%c", ch);  
    [NSThread sleepForTimeInterval:0.001];
}
 
- (void) run: (id) param  {  
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    int i;
    for(i=0; i<5; i++) {
        [ self print: '[' ];
        [ self print: 'M' ];
        [ self print: 'y' ];
        [ self print: ']' ];
        [ self print: '\n' ];
    }
    [pool release];
}
@end
int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool =
                           [[NSAutoreleasePool alloc] init];
   
    MyThread *myThread = [[MyThread alloc] init];

    [myThread startThread];
   
    int x;
    for(x=0;x<5;++x)
    {
        printf("Main thread x = %i\n",x);
        [NSThread sleepForTimeInterval: 0.001];
    }


    char ch;
    scanf("%c", &ch);             // Wait for key press


    [pool drain];
    return 0;
}
 


 

Note in the output below left, the primary and secondary thread execution is interleaved or parallel.

Each thread puts itself to sleep by:

[NSThread sleepForTimeInterval: 0.001];

allowing other, waiting threads to execute.

Without yielding execution by sleep, a single thread can execute to the exclusion of others, as illustrated at right below.

Output with sleep

Main thread x = 0
Main thread x = 1
[Main thread x = 2
MMain thread x = 3
yMain thread x = 4
]
[My]
[My]
[My]
[My]

Output without sleep

Main thread x = 0
Main thread x = 1
Main thread x = 2
Main thread x = 3
Main thread x = 4
[My]
[My]
[My]
[My]
[My]

Output with sleep and synchronization

Main thread x = 0
[My]
Main thread x = 1
[My]
Main thread x = 2
[My]
Main thread x = 3
[My]
Main thread x = 4
[My]

Question - Explain the outputs.

 

Synchronized Threads

Thread execution can be:

•independent of other threads (i.e. parallel or asynchronous execution)

•dependent (i.e. serialized or synchronous execution) where one thread executes to the exclusion of the other threads

•Thread execution can be controlled in Objective C using the synchronized statement which limits access to a common object to a single thread.

•The synchronization mechanism used is termed a monitor (versus a semaphore, task, or other mechanism)

•A monitor allows only one thread to have access to an object at a time.

•The keyword @synchronized defines a statement where one thread at a time has exclusive access to the object.

 

@synchronized

In the upper left output, Output with sleep, the primary and secondary thread output is interleaved.

Suppose the -run: method statements required atomic execution, that is all the statements were executed as a unit.

Objective C implements a simple monitor operator,@synchronized, for mutual exclusion on an object, very much as Java.

Note use of retain/release of the common object by both threads, this should always be done for shared objects to ensure that the object is not released by one thread while still in use by another.

 
#import <Foundation/Foundation.h>
 
@interface MyThread : NSObject {}
-(void) startThread: (NSString *) common;
-(void) run: (NSString *) common;
-(void) print: (char) ch;
@end
 
@implementation MyThread

- (void) startThread: (NSString *) common {
    NSThread* myThread = [[NSThread alloc] initWithTarget:self
                                                 selector:@selector(run:)
                                                 object: common];
    [myThread start]; 
}
 
-(void) print: (char) ch {
    printf("%c", ch);  
    [NSThread sleepForTimeInterval:0.001];
}
   
- (void) run: (NSString *) common  {
    [common retain]; 
    int i;
    for(i=0; i<5; i++)
@synchronized (common) {
            [ self print: '[' ];
            [ self print: 'M' ];
            [ self print: 'y' ];
            [ self print: ']' ];
            [ self print: '\n' ];
}

    [common release];
}
@end

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool =
                  [[NSAutoreleasePool alloc] init];
   
    MyThread *myThread = [[MyThread alloc] init];

    NSString *common = @"common object";

    [common retain];

    [myThread startThread: common];
 
    int x;
    for(x=0;x<5;++x)    {
@synchronized (common) {
            printf("Main thread x = %i\n",x);
}

        [NSThread sleepForTimeInterval: 0.001];
    }
 
    [common release];
    char ch;
    scanf("%c", &ch);

    [pool drain];
    return 0;
}

 
Output with sleep

Main thread x = 0
Main thread x = 1
[Main thread x = 2
MMain thread x = 3
yMain thread x = 4
]
[My]
[My]
[My]
[My]

Output without sleep

Main thread x = 0
Main thread x = 1
Main thread x = 2
Main thread x = 3
Main thread x = 4
[My]
[My]
[My]
[My]
[My]

Output with sleep and synchronization

Main thread x = 0
[My]
Main thread x = 1
[My]
Main thread x = 2
[My]
Main thread x = 3
[My]
Main thread x = 4
[My]

Question - Explain the outputs.

 

Why threads matter                iPhone unthreaded UI

Threads matter when more than one execution path is possible to be followed simultaneously. A good example is the UI, which should always be responsive.

Executing any blocking operation on the main or primary thread blocks the UI operation.

The following example illustrates that point.

The top progress bar is incremented within the following loop, updating the progress bar value at 1/4 second intervals, within the main thread. During that period, all UI operations, moving the bottom slider and the display of the progress bar, are blocked.

while ( [threadProgressView progress] < 1) {
       [NSThread sleepForTimeInterval: 0.25];
       [self updateProgressBar];
}
 #import "iPhoneUIViewController.h"

@implementation iPhoneUIViewController

@synthesize threadValueLabel, threadProgressView, testValueLabel, threadStartButton;

- (IBAction) startThreadButtonPressed:(UIButton *)sender {	
	threadStartButton.hidden = YES;
	threadValueLabel.text = @"0";
	threadProgressView.progress = 0.0;
	[self run: nil];
}

- (void) updateProgressBar {
	float actual = [threadProgressView progress];
	threadValueLabel.text = [NSString stringWithFormat:@"%.2f", actual];
	threadProgressView.progress = actual + 0.025;
}

- (void) run: (id) param  {
while ( [threadProgressView progress] < 1) {
      [NSThread sleepForTimeInterval: 0.25];
      [self updateProgressBar];
}
       threadStartButton.hidden = NO;	
} 

- (IBAction) testValueSliderChanged:(UISlider *)sender {	
	testValueLabel.text = [NSString stringWithFormat:@"%.2f", sender.value];
}

- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

- (void)dealloc {
	[threadValueLabel release];
	[threadProgressView release];
	[threadStartButton release];
	[testValueLabel release];
	[super dealloc];
}
@end

iPhone UI Update within a Thread        iPhone threaded UI

The previous example illustrated one threading issue in an event-driven UI such as the iPhone, that of updating the UI.

The following example updates a progress bar value in a secondary thread.

However, UI updates occur in the main or primary thread.

The following executes -updateProgressBar on the primary thread by:

while ( [threadProgressView progress] < 1) {
       [NSThread sleepForTimeInterval: 0.25];
       [self
          performSelectorOnMainThread: @selector(updateProgressBar)
                                              withObject: nil
                                         waitUntilDone: NO];
}

The effect is to call the -updateProgressBar method on the main or primary thread that handles the UI. The updates to the progress bar are displayed immediately, displaying a progressively moving progress bar.

However,

while ( [threadProgressView progress] < 1) {
       [NSThread sleepForTimeInterval: 0.25];
       [self updateProgressBar];
}

would display the progress bar only after completing the secondary thread. The effect would be to block the UI, only displaying the beginning and ending values.

#import "iPhoneUIViewController.h"
 
@implementation iPhoneUIViewController
 
@synthesize threadValueLabel, threadProgressView,
                    testValueLabel, threadStartButton;
 
- (void) startThread {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
    NSThread* myThread = [[NSThread alloc] initWithTarget:self
                                                 selector:@selector(run:)
                                                 object: nil];
    [myThread start];
    [pool release];
}
 
- (IBAction) startThreadButtonPressed:(UIButton *)sender { 
    threadStartButton.hidden = YES;
    threadValueLabel.text = @"0";
    threadProgressView.progress = 0.0;
    [self startThread];
}
 
- (void) updateProgressBar {
    float actual = [threadProgressView progress];
    threadValueLabel.text =
            [NSString stringWithFormat:@"%.2f", actual];
    threadProgressView.progress = actual + 0.025;
}

- (void) run: (id) param  {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];

    while ( [threadProgressView progress] < 1) {
       [NSThread sleepForTimeInterval: 0.25];
       [self
          performSelectorOnMainThread: @selector(updateProgressBar)
                                              withObject: nil
                                         waitUntilDone: NO];
    }

    threadStartButton.hidden = NO; 
    [pool release];
}
 
- (IBAction) testValueSliderChanged:(UISlider *)sender {   
    testValueLabel.text =
          [NSString stringWithFormat:@"%.2f", sender.value];
}
 
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}
- (void)dealloc {
    [threadValueLabel release];
    [threadProgressView release];
    [threadStartButton release];
    [testValueLabel release];
    [super dealloc];

@end

 

Thread-safe operations

Thread-safe operations are atomic in the sense that the operation is started and completed by the same thread, without another thread executing the same operation.

Objective C by default generates getter/setter atomic operations for properties:

@property (atomic, retain) (NSString *) x;
@synthesize x;

We have seen that @synchronized on a common object enforces atomic access, only one thread can access the common object at a time.

Producer/Consumer Model                    ProducerConsumer

The producer/consumer model is appropriate where one thread produces some data and another consumes the data.

The example below defines three thread-safe array access operations:

  1. The producer can always enqueue (add) an element.
  2. The consumer can only dequeue (remove) when an element exists in the array. Blocks until an element exists.
  3. The consumer can tryDequeue, dequeue if an element exists in array. Does not block but returns NULL if no element exists or queue locked.

A conditional lock (NSConditionLock) is used to indicate whether the array has elements, 0 for no elements, 1 for elements exist.

For NSConditionLock *theLock, the lock operations are:


The statement below deserves some discussion:

id element = [[[elements objectAtIndex:0] retain] autorelease];

[[elements objectAtIndex:0] retain] returns and retains the array element.

Adding an element to an NSMutableArray increments the retain counter, deallocating the array decrements the element's retain counter. Without the retain, the element returned could also be deallocated.

The element is retained and returned by dequeue method, autorelease delays the release until later, typically when [pool drain] executes.

 

@interface ThreadSafeQueue :
                                       NSObject {

  NSMutableArray *elements;

       // NO = no elements
       // YES = elements
  NSConditionLock *theLock;
}
 
-(id) init;

       // Enqueues an object
-(void) enqueue:(id)object;

       // Blocks until an object to return
-(id) dequeue;       

       // Returns NULL if queue is empty
-(id) tryDequeue;  

-(void) dealloc;
 
@end
@implementation ThreadSafeQueue
 
-(id) init {
  if (self = [super init]) {
    elements = [[NSMutableArray alloc] init];
    theLock = [[NSConditionLock alloc] initWithCondition: NO];
  }
  return self;
}
 
-(void) enqueue:(id)object {
  [theLock lock];
  [elements addObject: object];
  [theLock unlockWithCondition: YES];
}
 
-(id) dequeue {
  [theLock lockWhenCondition: YES];
id element = [[[elements objectAtIndex:0] retain] autorelease];

  [elements removeObjectAtIndex: 0];
  int count = [elements count];
  [theLock unlockWithCondition: (count > 0) ? YES : NO];
  return element;
}
 
-(id) tryDequeue {
  id element = NULL;
  if ([theLock tryLock]) {
    if ([theLock condition] == YES) {
      element = [[[elements objectAtIndex: 0] retain] autorelease];
      [elements removeObjectAtIndex: 0];
    }
    int count = [elements count];
    [theLock unlockWithCondition: (count > 0) ? YES : NO ];
  }
  return element;
}
 
-(void) dealloc {
  [elements release];
  [theLock release];
  [super dealloc];
}
@end

Example

The following demonstrates ThreadSafeQueue use.

Note:

The secondary thread, MyThread, dequeues five integers.

The primary thread in main enqueues five integers.

Output indicates dequeues are forced to wait until the queue holds a value.

enqueue +0
dequeue -0
enqueue +1
dequeue -1
enqueue +2
dequeue -2
enqueue +3
dequeue -3
enqueue +4
dequeue -4
ThreadSafeQueue *q;
 
@interface MyThread : NSObject {
        NSThread* myThread;
}
 -(void) startThread;
 -(void) run: (id) param;
@end
 
@implementation MyThread
- (void) startThread {
    NSLog(@"startMyThread");
    
    myThread = [[NSThread alloc] initWithTarget:self
                                           selector:@selector(run:)
                                           object: nil];
    [myThread start];
}
 
- (void) run: (id) param  {   
    NSAutoreleasePool *pool =
                                 [[NSAutoreleasePool alloc] init];
    int i;
    for(i=0;i<5;++i)  {
        printf("dequeue %d]\n",
               [(NSNumber *) [q dequeue] integerValue]);
        [NSThread sleepForTimeInterval: 0.001];
    }
    [pool release];
}  

-(void) dealloc {
    [myThread release];
    [super dealloc];
}
@end

int main (int argc, const char * argv[]) {
    NSAutoreleasePool * pool =
                      [[NSAutoreleasePool alloc] init];
   
    q = [[ThreadSafeQueue alloc] init];
   
    MyThread *myThread = [[MyThread alloc] init];
    [myThread startThread];
       
    int i;
    for(i=0; i<5; i++) {
        [q enqueue: [NSNumber numberWithInteger: i]];
        [NSThread sleepForTimeInterval: 0.001];
        printf("enqueue +%d\n",i);
    }

    [q release];
    [myThread release];
 
    [pool drain];
    return 0;
}

Delegates and Threads            iPhone Threaded XML Parser with Delegation

We have used NSURL and other classes that call delegate methods that we define and are called on some predetermined event.

Each defines an -initWithDelegate used to initialize the delegate method.

Importantly, when an event occurs, the delegate executes in the same thread that initialized the delegate method.

That makes sense in most cases, for example, a ViewController delegate method would be called on the main thread, allowing UI updates to occur.

Note that the UI can only be updated in the main thread.

In the following example, an XML file is downloaded from iTunes and parsed in a separate thread. When the parse is completed, a delegate method in the ViewController is called. See NSXMLParser for details on XML parsing.

The figure at right illustrates the concurrency of the two threads, while the XMLParser object is parsing in one thread, the UI is still active in the Main thread.

 

There are three main parts to delegation:

  1. Define the delegate method.
     
    -(void) parseDidComplete {
      [self updateTop10TextView];
      [xmlParser release];
    }

     

  2. Initialize the delegate. Note that thread is also initialized to the thread that calls -initWithDelegate
     
    xmlParser = [[XMLParser alloc] initWithDelegate: self];

    - (XMLParser *) initWithDelegate: (id) d {
      self = [super init];
      [self setDelegate: d];
      thread = [NSThread currentThread];
      return self;
    }

     

  3. Call the delegate on the delegate's thread.
     
      [delegate performSelector:@selector(parseDidComplete)
                                onThread: thread
                              withObject:nil
                         waitUntilDone:NO];
#import "XMLParser.h"

@implementation XMLParser
@synthesize delegate, URL;
- (XMLParser *) initWithDelegate: (id) d {
   self = [super init];
   [self setDelegate: d];
   thread = [NSThread currentThread];
   return self;
}
-(NSString *) getText { return outstring; }

- (void) run: (id) param  {
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	
    NSXMLParser *parser = 
         [[NSXMLParser alloc] initWithContentsOfURL: [self URL]];
    [parser setDelegate: self];
    [parser parse];
    [parser release];

    if ([delegate respondsToSelector:@selector(parseDidComplete)])
    [delegate performSelector:@selector(parseDidComplete)
                              onThread: thread
                            withObject: nil
                       waitUntilDone: NO];
     [pool release];
} 

- (void) parseXML: (NSURL *) url
{	
    NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
	
    outstring = [[NSMutableString alloc] init];
	
    [self setURL: url];
	
    NSThread* myThread = [[NSThread alloc] initWithTarget: self
                                                 selector:@selector(run:)
				         object: nil];
    [myThread start];
	
    [pool release];
}

- (void)parser:(NSXMLParser *)parser 
	didStartElement:(NSString *)elementName 
	namespaceURI:(NSString *)namespaceURI 
	qualifiedName:(NSString *)qName 
	attributes:(NSDictionary *)attributeDict
{
    if (qName) elementName = qName;
	if (elementName) 
		current = [NSString stringWithString:elementName];
}

- (void)parser:(NSXMLParser *)parser 
	didEndElement:(NSString *)elementName 
	namespaceURI:(NSString *)namespaceURI 
	qualifiedName:(NSString *)qName
{
	current = nil;
}

- (void)parser:(NSXMLParser *)parser 
 	foundCharacters:(NSString *)string
{
	if (!current) return;
	if ([current isEqualToString:@"title"]) 
		[outstring appendFormat:@"%@\n", string];
}

-(void) parserDidEndDocument:(NSXMLParser *)parser {}

-(void) parserDidStartDocument:(NSXMLParser *)parser {}

- (void) dealloc {
	[outstring release];
	[super dealloc];
}

@end
#import "iTunesTop10ViewController.h"

@implementation iTunesTop10ViewController

- (void)viewDidLoad {
    [super viewDidLoad];
	top10TextView.text = @"";
}

-(IBAction) loadButton: (id) send {
	top10TextView.text = @"";
	[activityIndicator startAnimating];
        xmlParser = [[XMLParser alloc] initWithDelegate: self];
	NSURL *url = 
               [NSURL URLWithString:
               @"http://ax.phobos.apple.com.edgesuite.net/WebObjects/MZStore.woa/wpa/MRSS/topsongs/limit=10/rss.xml"];
[xmlParser parseXML: url];
}	

-(void) updateTop10TextView {
	top10TextView.text = [xmlParser getText]; 
	[activityIndicator stopAnimating];
}
-(void) parseDidComplete {
   [self updateTop10TextView];
   [xmlParser release];
}
- (void)didReceiveMemoryWarning {
    [super didReceiveMemoryWarning];
}

- (void)viewDidUnload {}
- (void)dealloc {
    [super dealloc];
}

@end