Dispatch_semaphore_t reuse - What am I missing here?

I have code in which I use dispatch_semaphore_t to complete an input operation. When a semaphore is a member variable, it does not seem to behave correctly. I will show an example of code that works, and an example that doesn't 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 this code and the results that I get / get, I put breakpoints as described in my real code: one in the main function, one to run in the work function, one where the member semaphore is signaled, and two after waiting.

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

When I continue to work, I press the "START" breakpoint, then start, hit the "TASK_NOT_DONE" breakpoint, start and hit the "SIGNAL" breakpoint

That is, when I run a sequence using a semaphore that is a member, and do what looks like the correct signal / wait, the second time I try to wait on this semaphore, I seem to explode and it receives a signal after that as I came out of expectation.

It seems that I either do not control the counting rule (pairs of signals / expectations), or the semaphore of the members does not return to the state without signaling.

I feel that there is something fundamental here. Any input would be appreciated.

EDIT: Ultimately, what seemed to me missing was due to the fact that my actual code was a bit more complicated. Instead of a pure return from a difficult task, there are several threads and postNotification. I replaced postNotification with code in the notification handler - it sets a flag and signals a semaphore. This eliminates any delay that might have been sent by the notification handler.

+7
objective-c semaphore grand-central-dispatch nsthread
source share
3 answers

Yes, this is the expected behavior. If you expect a signal when the signal arrives, it will be caught by the next dispatch_semaphore_wait call for this 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 (ie 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 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 // signals and waits are unbalanced 

So, when you use the class instance variable, your getSomethingDoneUseMemberSemaphore behaves as above, where the second dispatch_semaphore_wait call will capture the first signal issued because (a) it is the same semaphore; and (b) if the timeout for the first call to dispatch_semaphore_signal .

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

+7
source share

When you call dispatch_semaphore_wait with a timeout and the thread is still blocked in timeout, it happens almost the same as if the semaphore_signal manager is called. One difference is that dispatch_semaphore_signal wakes up any thread, but a timeout wakes up that particular thread. Another difference is that dispatch_semaphore_wait will return a nonzero value instead of 0.

Here's the problem: the one who was going to call dispatch_semaphore_signal is still going to call it, and then we have one signal too much. This can be difficult to avoid; if you have a timeout of 10 seconds, then dispatch_semaphore_signal can be called after 10.000000001 seconds. Therefore, if you reuse the semaphore, you have a problem in your hands.

On the other hand, if you are not using semaphore reuse, the worst thing that happens is that the semaphore count is 1. But this is not a problem.

Summary: Do not reuse the semaphore if you are waiting for it with a timeout.

+2
source share

I managed to write something similar to what I think you are looking for, and it looks like it works the way you want it (but again, I'm not 100% sure, I understand what you are 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.

+1
source share

All Articles