Worker Pools in Objective-C

Our example demonstrates how to implement a worker pool using Grand Central Dispatch (GCD) and blocks in Objective-C.

#import <Foundation/Foundation.h>

@interface Worker : NSObject

- (void)processJobsWithQueue:(dispatch_queue_t)jobQueue
                resultsQueue:(dispatch_queue_t)resultsQueue;

@end

@implementation Worker

- (void)processJobsWithQueue:(dispatch_queue_t)jobQueue
                resultsQueue:(dispatch_queue_t)resultsQueue {
    dispatch_async(jobQueue, ^{
        NSInteger jobId;
        while ((jobId = [self getNextJob]) != NSNotFound) {
            NSLog(@"Worker %p started job %ld", self, (long)jobId);
            [NSThread sleepForTimeInterval:1.0]; // Simulate work
            NSLog(@"Worker %p finished job %ld", self, (long)jobId);
            
            dispatch_async(resultsQueue, ^{
                NSLog(@"Job %ld result: %ld", (long)jobId, (long)(jobId * 2));
            });
        }
    });
}

- (NSInteger)getNextJob {
    static NSInteger nextJob = 1;
    static dispatch_once_t onceToken;
    static dispatch_semaphore_t semaphore;
    
    dispatch_once(&onceToken, ^{
        semaphore = dispatch_semaphore_create(1);
    });
    
    dispatch_semaphore_wait(semaphore, DISPATCH_TIME_FOREVER);
    NSInteger job = (nextJob <= 5) ? nextJob++ : NSNotFound;
    dispatch_semaphore_signal(semaphore);
    
    return job;
}

@end

int main(int argc, const char * argv[]) {
    @autoreleasepool {
        dispatch_queue_t jobQueue = dispatch_queue_create("com.example.jobQueue", DISPATCH_QUEUE_CONCURRENT);
        dispatch_queue_t resultsQueue = dispatch_queue_create("com.example.resultsQueue", DISPATCH_QUEUE_SERIAL);
        
        NSMutableArray *workers = [NSMutableArray arrayWithCapacity:3];
        for (int i = 0; i < 3; i++) {
            Worker *worker = [[Worker alloc] init];
            [workers addObject:worker];
            [worker processJobsWithQueue:jobQueue resultsQueue:resultsQueue];
        }
        
        dispatch_group_t group = dispatch_group_create();
        dispatch_group_enter(group);
        
        dispatch_async(resultsQueue, ^{
            NSInteger completedJobs = 0;
            while (completedJobs < 5) {
                [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
                completedJobs++;
            }
            dispatch_group_leave(group);
        });
        
        dispatch_group_wait(group, DISPATCH_TIME_FOREVER);
        NSLog(@"All jobs completed");
    }
    return 0;
}

In this Objective-C implementation:

  1. We define a Worker class that processes jobs from a concurrent queue and sends results to a serial queue.

  2. The processJobsWithQueue:resultsQueue: method runs in a loop, fetching jobs and processing them. It simulates work by sleeping for a second.

  3. The getNextJob method uses a semaphore to safely distribute job IDs across multiple workers.

  4. In the main function, we create concurrent and serial dispatch queues for jobs and results, respectively.

  5. We create three worker instances and start them processing jobs.

  6. We use a dispatch group to wait for all jobs to complete before exiting the program.

This implementation showcases Objective-C’s concurrency features using Grand Central Dispatch (GCD). It creates a pool of workers that process jobs concurrently, demonstrating a pattern similar to the original example.

When you run this program, you’ll see output showing the 5 jobs being executed by various workers. The program takes about 2 seconds to complete, despite doing about 5 seconds of total work, because there are 3 workers operating concurrently.

To compile and run the program:

$ clang -framework Foundation worker_pool.m -o worker_pool
$ ./worker_pool

This example demonstrates how to implement concurrent work processing in Objective-C using GCD, which is the idiomatic way to handle concurrency in this language.