Question

I want to achieve the following task using NSThread

I've a main thread and three (3) workerThread T1, T2, T3. All these started at the same time from main thread, Main thread has an int size variable. Now I want to synchronize all three threads in a way they, when my each of the above threads will execute, it will print the following:

//in main thread

- (void) mainFunction{
    size = 0;

    NSThread* T1 = [[NSThread alloc] initWithTarget:self
                                           selector:@selector(workerThread:)
                                             object:@"T1"];
    [T1 start];


    NSThread* T2 = [[NSThread alloc] initWithTarget:self
                                           selector:@selector(workerThread:)
                                             object:@"T2"];
    [T2 start];

    NSThread* T3 = [[NSThread alloc] initWithTarget:self
                                           selector:@selector(workerThread:)
                                             object:@"T3"];
    [T3 start];


}

// worker thread

- (void) workerThread:(id)obj{

    size++;
    NSLog(@"Thread:%@--------Size:%d,obj,size)

}

I want following output:

Thread:T1-------Size:1
Thread:T2-------Size:2
Thread:T3-------Size:3

Thread:T1-------Size:4
Thread:T2-------Size:5
Thread:T3-------Size:6

Thread:T1-------Size:7
Thread:T2-------Size:8
Thread:T3-------Size:9

and return the control back to main thread when size=10

Was it helpful?

Solution

A couple of thoughts:

  1. You say "return control back to main thread when size=10". That doesn't quite make sense. The main thread never "lost" control (as these threads are happening concurrently). Perhaps you wanted something to happen on the main thread when this situation arose?

  2. You're not having the workerThread method do any looping, so as you've written it, each thread will do this once and then quit. You probably need to add some form of loop here.

  3. Even if you added looping, your desired output suggests that you're assuming a particular sequence of actions that would take place, namely that these three threads will run in order (but you have no such assurances). If you needed that behavior, you'd set up a series of semaphores by which you could have one thread waiting for a signal to be sent by another.

  4. You should be careful when updating a variable from different threads. See the Synchronization section of the Threading Programming Guide. It's simplified when dealing with a fundamental data type like your counter (just make sure you declare it as atomic). But in more substantive scenarios, you'll want to employ some synchronization technique such as @synchronized, locks, dedicated custom serial queue, etc.

  5. In general, if you're using threads (but not necessary if using queues), you should be creating an autorelease pool for your thread.

Anyway, with these observations aside, you might have something like the following, which (a) has @autoreleasepool; (b) loops; and (c) uses a lock to make sure that the various threads synchronize their interactions with the size variable:

- (void) workerThread:(id)obj
{
    @autoreleasepool {

        BOOL done = NO;

        while (!done) {

            [self.lock lock];

            if (size < 9) {
                size++;
                NSLog(@"Thread:%@--------Size:%d", obj, size);
            }
            else
            {
                done = YES;
            }

            [self.lock unlock];

            // perhaps you're doing something time consuming here...
        }
    }
}

This assumes you have NSLock property, called lock:

@property (nonatomic, strong) NSLock *lock;

Which you created before initiating your thread test:

- (void) threadTest
{
    size = 0;

    self.lock = [[NSLock alloc] init];

    NSThread* T1 = [[NSThread alloc] initWithTarget:self selector:@selector(workerThread:) object:@"T1"];
    [T1 start];

    NSThread* T2 = [[NSThread alloc] initWithTarget:self selector:@selector(workerThread:) object:@"T2"];
    [T2 start];

    NSThread* T3 = [[NSThread alloc] initWithTarget:self selector:@selector(workerThread:) object:@"T3"];
    [T3 start];
}

Having said all this, you started this with "return control back to the main thread". As I said earlier, there's really no need to do that because in your example, your app's main thread never yielded control

For controlling relationships between tasks happening on different threads, I might suggest using GCD or operation queues. They're easier to use and have better mechanism for controlling dependencies between various tasks/operations (see Concurrency Programming Guide).

For example, consider the operation-based equivalent to your above workerThread method (identical, but no autorelease pool is needed):

- (void) operationMethod:(id)obj
{
    BOOL done = NO;

    while (!done) {

        [self.lock lock];

        if (size < 9) {
            size++;
            NSLog(@"Operation:%@--------Size:%d", obj, size);
        }
        else
        {
            done = YES;
        }

        [self.lock unlock];

        // perhaps you're doing something time consuming here...
    }
}

You could then create three operations (which probably will run on three threads) and wait for the result, like so:

- (void) operationTestWait
{
    size = 0;

    self.lock = [[NSLock alloc] init];

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    NSOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationMethod:) object:@"Op1"];
    NSOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationMethod:) object:@"Op2"];
    NSOperation *op3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationMethod:) object:@"Op3"];

    [queue addOperations:@[op1, op2, op3] waitUntilFinished:YES];

    // do here whatever should happen when the operations are done
}

In this case, the main thread will wait for these three operations to finish.

Or, better, if these tasks take more than a few milliseconds, you should not have the main queue wait (since you should never block the main queue), but rather, you should simply define what you want the main queue to do when the three operations are done:

- (void) operationTest
{
    size = 0;

    self.lock = [[NSLock alloc] init];

    NSOperationQueue *queue = [[NSOperationQueue alloc] init];

    NSOperation *op1 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationMethod:) object:@"Op1"];
    NSOperation *op2 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationMethod:) object:@"Op2"];
    NSOperation *op3 = [[NSInvocationOperation alloc] initWithTarget:self selector:@selector(operationMethod:) object:@"Op3"];

    NSOperation *completion = [NSBlockOperation blockOperationWithBlock:^{

        // if you want this to do something on the main queue, then have this add it to the main queue

        [[NSOperationQueue mainQueue] addOperationWithBlock:^{
            // do here whatever should happen when the operations are done
        }];
    }];

    // define this completion operation to be dependent upon the above three operations

    [completion addDependency:op1];
    [completion addDependency:op2];
    [completion addDependency:op3];

    // now add all of them, but don't wait until finished;
    // but the completion operation will only start when its dependencies
    // are resolved

    [queue addOperations:@[op1, op2, op3, completion] waitUntilFinished:NO];
}

Forgive the long-winded answer. If you can give us a more practical example of what these various threads will be doing and we can provide better counsel on how to best tackle it. But, in general, operation queues or dispatch queues will probably be more efficient way to tackle most concurrency related challenges.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top