dispatch_semaphore_t reuse - What am I missing here?

GTAE86 picture GTAE86 · Aug 21, 2013 · Viewed 12.2k times · Source

I have some code where I am using dispatch_semaphore_t to signal operation completion. When the semaphore is a member variable, it does not seem to behave correctly. I will show example code that works and an example that does not seem to work:

@implementation someClass  
{  
  dispatch_semaphore_t memberSem;  
  dispatch_semaphore_t* semPtr;  
  NSThread* worker;
  BOOL taskDone;  
}  

- (id)init  
{  
  // Set up the worker thread and launch it - not shown here.  
  memberSem= dispatch_semaphore_create(0); 
  semPtr= NULL;  
  taskDone= FALSE;  
}  

- (void)dealloc  
{  
  // Clean up the worker thread as needed - not shown here.  
  if((NULL != semPtr) && (NULL != *semPtr))  
    disptatch_release(*semPtr);  

  dispatch_release(memberSem);  
}  

- (void)doSomethingArduous  
{  
  while([self notDone])  // Does something like check a limit.  
    [self doIt];  // Does something like process data and increment a counter.  

  taskDone= TRUE;  // I know this should be protected, but keeping the example simple for now.  

  if((NULL != semPtr) && (NULL != *semPtr))  
    dispatch_semaphore_signal(*semPtr);  // I will put a breakpoint here, call it  "SIGNAL"  
}  

- (BOOL)getSomethingDoneUseLocalSemaphore  
{  
  taskDone= FALSE;  // I know this should be protected, but keeping the example simple for now.  
  dispatch_semaphore_t localSem= dispatch_semaphore_create(0);  
  semPtr= &localSem;  
  [self performSelector:doSomethingArduous onThread:worker withObject:nil waitUntilDone:NO];  

  dispatch_time_t timeUp= dispatch_time(DISPATCH_TIME_NOW, (uint64_t)(2.5 * NSEC_PER_SEC));  
  dispatch_semaphore_wait(localSem, timeUp);  

  semPtr= NULL;  
  dispatch_release(localSem);  

  // I know I could just return taskDone. The example is this way to show what the problem is.  
  if(taskDone)  // Again with thread safety.  
    return TRUE;    

  return FALSE;  
}  

- (BOOL)getSomethingDoneUseMemberSemaphore  
{  
  taskDone= FALSE;  // I know this should be protected, but keeping the example simple for now.  

  semPtr= &memberSem;  // I will put a breakpoint here, call it "START"  
  [self performSelector:doSomethingArduous onThread:worker withObject:nil waitUntilDone:NO];  

  dispatch_time_t timeUp= dispatch_time(DISPATCH_TIME_NOW, (uint64_t)(2.5 * NSEC_PER_SEC));  
  dispatch_semaphore_wait(memberSem, timeUp);  

  semPtr= NULL;  

  // I know I could just return taskDone. The example is this way to show what the problem is.  
  if(taskDone)  // Again with thread safety.  
    return TRUE;  // I will put a breakpoint here, call it "TASK_DONE"   

  return FALSE;  // I will put a breakpoint here, call it "TASK_NOT_DONE"  
}  

- (void)hereIsWhereWeBringItTogether  
{  
  BOOL gotItDoneLocal= [self getSomethingDoneUseLocalSemaphore];  // Will return TRUE.  
  gotItDoneLocal= [self getSomethingDoneUseLocalSemaphore];  // Will return TRUE.  
  gotItDoneLocal= [self getSomethingDoneUseLocalSemaphore];  // Will return TRUE.  

  BOOL gotItDoneMember= [self getSomethingDoneUseMemberSemaphore];  // Will return TRUE. I will put a breakpoint here, call it "RUN_TEST"  
  gotItDoneMember= [self getSomethingDoneUseMemberSemaphore];  // Will return FALSE.  
}  

So, given that code and the results I get/got, I put the breakpoints as described in my real code: One in the main function, one to start in the work function, one where the member semaphore is signaled, and two after the wait.

What I found was in the case where I use the member semaphore, in the first round I stop at breakpoint "RUN_TEST", run and hit breakpoint "START", run then hit breakpoint "SIGNAL", run then hit breakpoint "TASK_DONE" - all as expected.

When I continue to run, I hit breakpoint "START", run then hit breakpoint "TASK_NOT_DONE", run then hit breakpoint "SIGNAL"

That is, when I run the sequence using a semaphore that is a member, and do what looks like proper signal/wait, the second time I try to wait on that semaphore I seem to blow by and it gets signaled after I have exited the wait.

I seem to either be not managing the counting right (signal/wait pairings) or that member semaphore will not go back to an un-signaled state.

My feeling is there is something fundamental I am missing here. Any input would be appreciated.

EDIT: Ultimately what I seemed to be missing was due to my actual code being a bit more complicated. Instead of a clean return from the arduous task, there are multiple threads involved and a postNotification. I replaced the postNotification with the code in the notification handler - it sets a flag and signals the semaphore. That way any delay that might have been introduced by the notification handler is eliminated.

Answer

Rob picture Rob · Aug 21, 2013

Yes, this is the expected behavior. If you time out waiting for a signal, when the signal comes it, it will be caught by the next call to dispatch_semaphore_wait for that particular semaphore. Consider the following example:

For example:

dispatch_semaphore_t semaphore = dispatch_semaphore_create(0);
dispatch_time_t timeout;

// in 5 seconds, issue signal

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(5);
    NSLog(@"Signal 1");
    dispatch_semaphore_signal(semaphore);
});

// wait four seconds for signal (i.e. we're going to time out before the signal)

NSLog(@"Waiting 1");
timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC));
if (dispatch_semaphore_wait(semaphore, timeout))
    NSLog(@"Waiting for 1: timed out");
else
    NSLog(@"Waiting for 1: caught signal");

// now, let's issue a second signal in another five seconds

dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
    sleep(5);
    NSLog(@"Signal 2");
    dispatch_semaphore_signal(semaphore);
});

// wait another four seconds for signal

// this time we're not going to time out waiting for the second signal, 
// because we'll actually catch that first signal, "signal 1")

NSLog(@"Waiting 2");
timeout = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(4.0 * NSEC_PER_SEC));
if (dispatch_semaphore_wait(semaphore, timeout))
    NSLog(@"Waiting for 2: timed out");
else
    NSLog(@"Waiting for 2: caught signal");

// note, "signal 2" is still forthcoming and the above code's 
// signals and waits are unbalanced 

So, when you use the class instance variable, your getSomethingDoneUseMemberSemaphore behaves like above, where the second call to dispatch_semaphore_wait will catch the first signal issued because (a) it's the same semaphore; and (b) if the first call to dispatch_semaphore_signal timed out.

But if you use unique semaphores each time, then the second call to dispatch_semaphore_wait will not respond to the dispatch_semaphore_signal of the first semaphore.