Question

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.

Was it helpful?

Solution

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.

OTHER TIPS

When you call dispatch_semaphore_wait with a timeout, and the thread is still blocked at the timeout, what happens is almost the same as if dispatch_semaphore_signal had been called. One difference is that dispatch_semaphore_signal would wake up any thread, but the timeout wakes up this particular thread. The other difference is that dispatch_semaphore_wait will return a non-zero value instead of 0.

Here's the problem: Whoever was going to call dispatch_semaphore_signal is still going to call it, and then we have one signal too many. This may be hard to avoid; if you have a 10 second timeout then dispatch_semaphore_signal could be called after 10.000000001 seconds. So if you are reusing the semaphore, you have a problem at your hand.

On the other hand, if you are not reusing the semaphore then the worst that happens is that the semaphore count goes to 1. But that's no problem.

Summary: Don't reuse a semaphore if you wait for it with a timeout.

I was able to code up something akin to what I think you're looking for and it appears to work the way you want it to (but again, I'm not 100% sure I understand what you're looking for.):

ArduousTaskDoer.m

@implementation ArduousTaskDoer
{
    dispatch_semaphore_t mSemaphore;
    BOOL mWorkInProgress;
}

- (id)init
{
    if (self = [super init])
    {
        mSemaphore = dispatch_semaphore_create(0);
    }
    return self;
}

- (void)dealloc
{
    mSemaphore = nil;
}

- (void)doWork
{
    @synchronized(self)
    {
        mWorkInProgress = YES;
    }

    // Do stuff
    sleep(10);

    @synchronized(self)
    {
        mWorkInProgress = NO;
    }

    dispatch_semaphore_signal(mSemaphore);
}

- (BOOL)workIsDone
{

    @synchronized(self)
    {
        if (!mWorkInProgress)
        {
            mWorkInProgress = YES;
            dispatch_async(dispatch_get_global_queue(0, 0), ^{
                [self doWork];
            });
        }
    }


    if (dispatch_semaphore_wait(mSemaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)2.5 * NSEC_PER_SEC)))
    {
        return NO;
    }

    return YES;
}

@end

...and then the calling code:

ArduousTaskDoer* task = [[ArduousTaskDoer alloc] init];
BOOL isDone = NO;
while(!(isDone = [task workIsDone]))
{
    NSLog(@"Work not done");
}

NSLog(@"Work is done");

// Do it again... Semaphore is being reused
while(!(isDone = [task workIsDone]))
{
    NSLog(@"Work not done");
}

NSLog(@"Work is done");

Hope this helps.

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