Search code examples
objective-ciphonemultithreadingthread-safetynscondition

Unlocking a thread using NSCondition (Objective-C)


I am using Objective-C and was trying to work with threads which need to talk to each other.

The code is as follows:

-(id) init
{
    self = [super init];
    if (self) {
        _event = [[MyTestClass alloc]init]; //MyTestClass has a property of type NSCondition
    }
    return self;
}


-(void) func1
{
NSLog(@"The Function 1 is being called");
NSLog(@"Locking The First function");
[self.event.myCondition lock];
[self.event.myCondition wait];
NSLog(@"Resuming Function 1");
    NSLog(@"This is a test 1 ");
    NSLog(@"This is a test 2");
    NSLog(@"Terminating func 1");
}
-(void) func2
{
    NSLog(@"2");
    NSLog(@"Hey Hey Hey How are you 0 ");
    NSLog(@"Hey Hey Hey How are you 1 ");
    NSLog(@"Hey Hey Hey How are you 2");
    NSLog(@"Signaling function 1");
    [self.event.myCondition signal];
    NSLog(@"Hey Hey Hey How are you 3");
    NSLog(@"Terminating func 2");
}

I run func1 and func2 on two separate threads as below

- (void)viewDidLoad {
    [super viewDidLoad];
    SuperTestClass * n = [[SuperTestClass alloc]init];
    // Do any additional setup after loading the view, typically from a nib.
    MyTestClass * m = [[MyTestClass alloc]init];
    dispatch_queue_t newQueue =     dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT,0);
    dispatch_group_t newGroup = dispatch_group_create();
    dispatch_group_async(newGroup, newQueue, ^{
        [n func1];
    });
    dispatch_group_async(newGroup, newQueue, ^{
        [n func2];
    });
    dispatch_group_wait(newGroup, DISPATCH_TIME_FOREVER);
    NSLog(@"All process have terminated");
}

I get the following error when I run this code

2015-07-07 19:05:54.528 signalingInObjectiveC[31617:319892] 2
2015-07-07 19:05:54.528 signalingInObjectiveC[31617:319894] The Function 1 is being called
2015-07-07 19:05:54.529 signalingInObjectiveC[31617:319892] Hey Hey Hey How are you 0 
2015-07-07 19:05:54.529 signalingInObjectiveC[31617:319894] Locking The First function
2015-07-07 19:05:54.529 signalingInObjectiveC[31617:319892] Hey Hey Hey How are you 1 
2015-07-07 19:05:54.529 signalingInObjectiveC[31617:319892] Hey Hey Hey How are you 2 
2015-07-07 19:05:54.530 signalingInObjectiveC[31617:319892] Signaling function 1
2015-07-07 19:05:54.530 signalingInObjectiveC[31617:319892] Hey Hey Hey How are you 3 
2015-07-07 19:05:54.530 signalingInObjectiveC[31617:319894] Resuming Function 1
2015-07-07 19:05:54.530 signalingInObjectiveC[31617:319892] Terminating func 2
2015-07-07 19:05:54.530 signalingInObjectiveC[31617:319894] This is a test 1
2015-07-07 19:05:54.530 signalingInObjectiveC[31617:319894] This is a test 2
2015-07-07 19:05:54.530 signalingInObjectiveC[31617:319894] Terminating func 1
2015-07-07 19:05:54.530 signalingInObjectiveC[31617:319870] All process have terminated
2015-07-07 19:05:54.535 signalingInObjectiveC[31617:319870] *** -[NSCondition dealloc]: condition (<NSCondition: 0x7fa2aa517180> '(null)') deallocated while still in use
2015-07-07 19:05:54.535 signalingInObjectiveC[31617:319870] *** Break on _NSLockError() to debug.

I'm wondering what's wrong in the way I am unlocking.


Solution

  • You are misusing the NSCondition class. Check the apple doc. In essence, a POSIX condition works with a mutex that protects the shared data that the condition tests. You are supposed to have something like (where P is a boolean predicate on the shared state)

    Thread #1:

    [cond lock];
    while (!P(state)) {
       [cond wait];
    }
    // invariant: if you get here, P(state) is true
    ... Use the state... // it is protected by the lock
    [cond unlock];
    

    Thread #2:

    [cond lock];
    ... change the state ... // this potentially changes the value of P
    [cond signal];  // or [cond broadcast]
    [cond unlock];
    

    Thread #2 modifies the shared state (e.g., deposits a message in the shared buf) and notifies thread #1. Thread #1 cannot wake up until thread #2 relinquishes the lock. Note how the call [cond wait] in thread #1 atomically unlocks the mutex and falls asleep. The loop is necessary for two reasons:

    1. You can get spurious wakeup (for no reason, so there is no guarantee that P(state) is true.
    2. You could have several threads doing what is described by thread #1. If thread #2 deposits only one piece of data, only one of the threads doing the consumption can actually pick up a data item and "consuming it" will invalidate P(state), so the others would go right back to sleep.

    Bottom line, your code is incorrect because you fail to unlock the condition and you destroy it while locked. You didn't follow the coding pattern for using a POSIX condition.