Concurrent Programming 6:Using Threads for Concurrency

来源:互联网 发布:java用正则表达式 编辑:程序博客网 时间:2024/04/30 11:40

Using Threads for Concurrency

Now that you have learned about threads and synchronization, you will implement an example program that performs concurrent processing using threads and these synchronization mechanisms. In Xcode, create a new project by selecting New image Project . . .from the Xcode File menu. In the New Project Assistant pane, create a command-line application. In the Project Options window, specify ConcurrentThreads for the Product Name, choose Foundation for the Project Type, and select ARC memory management by checking the Use Automatic Reference Counting check box. Specify the location in your file system where you want the project to be created (if necessary, select New Folder and enter the name and location for the folder), uncheck the Source Control check box, and then click the Create button.

Next, you will create the class with a method that will be executed in a separate thread. SelectNew image File . . . from the Xcode File menu, select the Objective-C class template, and name the class ConcurrentProcessor. Select the ConcurrentThreads folder for the files location and the ConcurrentThreads project as the target, and then click the Create button. In the Xcode project navigator pane, select the ConcurrentProcessor.h file and update the class interface, as shown in Listing 17-13.

Listing 17-13.  ConcurrentProcessor Interface

#import <Foundation/Foundation.h>@interface ConcurrentProcessor : NSObject@property (readwrite) BOOL isFinished;@property (readonly) NSInteger computeResult;- (void)computeTask:(id)data;@end

The interface declares two properties and a single method. The method computeTask: is the method that will be executed in a separate thread. This method performs computations, where its input parameter is the number of computations to perform. The propertyisFinished is used to signal completion of the computations in the thread(s) that execute the method. The property computeResult contains the results of the computation. OK, now using the Xcode project navigator, select the ConcurrentProcessor.m file and update it as shown in Listing 17-14.

Listing 17-14.  ConcurrentProcessor Implementation

#import "ConcurrentProcessor.h"@interface ConcurrentProcessor()@property (readwrite) NSInteger computeResult;@end@implementation ConcurrentProcessor{  NSString *computeID;        // Unique object for @synchronize lock  NSUInteger computeTasks;    // Count of number of concurrent compute tasks  NSLock *computeLock;        // lock object}- (id)init{  if ((self = [super init]))  {    _isFinished = NO;    _computeResult = 0;    computeLock = [NSLock new];    computeID = @"1";    computeTasks = 0;  }  return self;}- (void)computeTask:(id)data{  NSAssert(([data isKindOfClass:[NSNumber class]]), @"Not an NSNumber instance");  NSUInteger computations = [data unsignedIntegerValue];  @autoreleasepool  {    @try    {      // Obtain lock and increment number of active tasks      if ([[NSThread currentThread] isCancelled])      {        return;      }      @synchronized(computeID)      {        computeTasks++;      }      // Obtain lock and perform computation in critical section      [computeLock lock];      if ([[NSThread currentThread] isCancelled])      {        [computeLock unlock];        return;      }      NSLog(@"Performing computations");      for (int ii=0; ii<computations; ii++)      {        self.computeResult = self.computeResult + 1;      }      [computeLock unlock];      // Simulate additional processing time (outside of critical section)      [NSThread sleepForTimeInterval:1.0];             // Decrement number of active tasks, if none left update flag      @synchronized(computeID)      {        computeTasks--;        if (!computeTasks)        {          self.isFinished = YES;        }      }    }    @catch (NSException *ex) {}  }}@end

The file begins by declaring a class extension that enables write access for thecomputeResult property. Next, the implementation begins by declaring several private instance variables used for thread management and synchronization. Of note is thecomputeTasks variable; it contains a count of the number of threads concurrently executing the computeTask: method. The init method initializes ConcurrentProcessor objects, setting variables to the appropriate initial values.

Now let’s examine the computeTask: method. First, observe that the method is surrounded by an autorelease pool and a try-catch exception block. These are required to ensure that objects are not leaked from the thread in which the method executes and that it handles any thrown exceptions (each thread is responsible for handling its own exceptions). Because this method can be executed concurrently by multiple threads and it also accesses and updates shared data, access to this data must be synchronized. The code uses the @synchronizeddirective to control access to the computeTasks variable, thereby enabling it to be updated by one thread at a time.

@synchronized(computeID){  computeTasks++;}

The method is also implemented to support thread cancellation, and thus periodically checks the state of the thread and exits if it is cancelled.

if ([[NSThread currentThread] isCancelled]){  return;}

Next, the method contains code to perform its computations. This simple computation merely increments the value of the computeResult property the number of times specified by the method’s input parameter. This code must be performed within a critical section to enforce synchronized access to its shared data. The code acquires a lock to the NSLock instance. Once the lock is obtained, it tests to see if the thread has been cancelled, and if so, it releases the lock and exits the thread without performing its computations.

[computeLock lock];if ([[NSThread currentThread] isCancelled]){  [computeLock unlock];  return;}

The code then performs its computations and releases the lock. Next, the thread pauses for one second to simulate additional processing performed outside of the critical section (hence concurrently).

[computeLock unlock];[NSThread sleepForTimeInterval:1.0];

The method concludes by decrementing the number of threads executing it, and setting theisFinished property if none remain. This logic is all implemented within a synchronized block to ensure access by only one thread at a time.

@synchronized(computeID){  computeTasks--;  if (!computeTasks)  {    self.isFinished = YES;  }}

Now that you have finished implementing the ConcurrentProcessor class, let’s move on to the main() function. In the Xcode project navigator, select the main.m file and update themain() function, as shown in Listing 17-15.

Listing 17-15.  ConcurrentThreads main( ) Function

#import <Foundation/Foundation.h>#import "ConcurrentProcessor.h"int main(int argc, const char * argv[]){  @autoreleasepool  {    ConcurrentProcessor *processor = [ConcurrentProcessor new];    [processor performSelectorInBackground:@selector(computeTask:)                                withObject:[NSNumber numberWithUnsignedInt:5]];    [processor performSelectorInBackground:@selector(computeTask:)                                withObject:[NSNumber numberWithUnsignedInt:10]];    [processor performSelectorInBackground:@selector(computeTask:)                                withObject:[NSNumber numberWithUnsignedInt:20]];    while (!processor.isFinished)      ;    NSLog(@"Computation result = %ld", processor.computeResult);  }  return 0;}

The main() function begins by creating a ConcurrentProcessor object. It then executes itscomputeTask: method with a new background thread using itsperformSelectorInBackground: method. This method is executed with three separate threads, each time providing a different input value for the number of computations performed. The function then uses a conditional expression to test if all of the threads have finished executing the computeTask: method. Once this occurs, the result of the computation is logged to the output pane.

When you compile and run the program, you should observe the messages in the output pane shown in Figure 17-5.

9781430250500_Fig17-05.jpg

Figure 17-5. ConcurrentThreads program output

This program demonstrates the use of threads to perform concurrent programming. It also illustrates some of the complexities involved with thread management and synchronization. In the next section, you will learn about a different mechanism for concurrent programming, operations and operation queues.