Search code examples
objective-clockingsignalsnsconditionios-multithreading

NSCondtion with multiple threads


method1:

 - (void) method1
 {
    [_condition lock];

    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(5 * NSEC_PER_MSEC)), dispatch_get_main_queue(), ^{
         //Fetach data from remote, when finished call method2
         [self fetchData]; 
    });

    [_condition waitUntilDate:[NSDate dateWithTimeIntervalSinceNow:30.0]];

    // Do something.
    [_condition unlock];
}

method2:

- (void) method2
 {
    [_condition lock];
    [_condition signal];
    [_condition unlock];
}

If Thread 1 is in method1, by executing [_condition waitUtilDate ...]; it unlocks its lock. Thread 2 entered this area and also wait on the condition by executing [_condition waitUtilDate ...].

Both Thread 1 and Thread 2 enqueued a Block(request 1 request 2) to fetch the same data from remote. When request 1 finishes, it calls method2 to signal _condition:

My questions are:

  • Which will be signaled, Thread 1 or Thread 2 ?
  • 'cause request 1 and request 2 are doing the same thing, I can signal both threads(broadcast) and cancel request 2 when request 1 finishes. But, a better way is to refuse Thread 2 to enter the critical area, after request 1 is sent out. But I coundn't lock twice before entering the critical area. So what can I do?

Thanks.


Solution

  • If you're trying to prevent duplicate requests you can do this with a boolean flag that you set when starting the request and then clear when you're done. You'd protect your critical regions with a mutex/semaphore/NSLock/etc.

    [_lock lock];
    if (_fetching) {
       [_lock unlock];
       return;
    }
    
    _fetching = YES;
    
    [self startRequestWithCompletion: ^{ 
       [_lock lock];
       _fetching = NO;
       [_lock unlock]; 
    }];
    
    [_lock unlock];
    

    Or you can use an NSOperationQueue and NSOperation to handle this more elegantly. You'd test the queue to see if there are any pending or running operations before starting a new one.

    if (_operationQueue.operationCount)
        return;
    
    [_operationQueue addOperationWithBlock: ^{
        // get your data
    }];
    

    How you do this really depends on exactly what you're fetching and whether the response may change based on input data. For example, I use NSOperations extensively to manage background computations. However, user input could change the result of those computations and invalidate any previous work. I hang on to a reference the NSOperation itself so I can cancel it and restart a new one where appropriate.