dispatch_semaphore_t重用 - 我在这里缺少什么?

时间:2013-08-21 17:25:28

标签: objective-c grand-central-dispatch semaphore nsthread

我有一些代码,我使用dispatch_semaphore_t来表示操作完成。当信号量是成员变量时,它似乎行为不正确。我将展示有效的示例代码和一个似乎不起作用的示例:

@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.  
}  

所以,考虑到代码和我得到/得到的结果,我按照我的真实代码中描述的断点:一个在main函数中,一个在工作函数中开始,一个是成员信号量发出信号,以及两个等待之后。

我发现的是在我使用成员信号量的情况下,在第一轮我停在断点“RUN_TEST”,运行并点击断点“START”,然后运行命中断点“SIGNAL”,然后运行命中断点“ TASK_DONE“ - 一切尽在预料之中。

当我继续运行时,我点击断点“START”,然后运行命中断点“TASK_NOT_DONE”,然后运行命中断点“SIGNAL”

也就是说,当我使用作为成员的信号量运行序列,并且执行看起来像正确的信号/等待时,我第二次尝试等待该信号量时,我似乎在吹嘘并且它在我之后发出信号已退出等待。

我似乎要么不管理计数权(信号/等待配对),要么成员信号量不会回到无信号状态。

我的感觉是我在这里缺少一些基本的东西。任何意见都将不胜感激。

编辑:最终我似乎缺少的是由于我的实际代码有点复杂。而不是从艰巨的任务中获得干净的回报,而是涉及多个线程和一个postNotification。我用通知处理程序中的代码替换了postNotification - 它设置了一个标志并用信号通知信号量。这样就消除了通知处理程序可能引入的任何延迟。

3 个答案:

答案 0 :(得分:9)

是的,这是预期的行为。如果你等待一个信号超时,当信号到来时,它会被下一次调用dispatch_semaphore_wait来捕获该特定信号量。请考虑以下示例:

例如:

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 

因此,当您使用类实例变量时,您的getSomethingDoneUseMemberSemaphore的行为与上面类似,第二次调用dispatch_semaphore_wait将捕获发出的第一个信号,因为(a)它是相同的信号量; (b)如果第一次致电dispatch_semaphore_signal超时。

但是,如果您每次都使用唯一的信号量,那么第二次调用dispatch_semaphore_wait将不会响应第一个信号量的dispatch_semaphore_signal

答案 1 :(得分:2)

当您使用超时调用dispatch_semaphore_wait并且该线程在超时时仍然被阻塞时,所发生的情况几乎与调用dispatch_semaphore_signal时相同。一个区别是dispatch_semaphore_signal会唤醒任何线程,但超时会唤醒此特定线程。另一个区别是dispatch_semaphore_wait将返回非零值而不是0.

问题在于:谁打算调用dispatch_semaphore_signal仍然会调用它,然后我们有一个信号太多了。这可能很难避免;如果你有10秒的超时,那么在10.000000001秒之后可以调用dispatch_semaphore_signal。因此,如果您正在重复使用信号量,那么您手头就有问题。

另一方面,如果你没有重复使用信号量,那么最糟糕的情况是信号量计数变为1.但这没有问题。

总结:如果您等待超时,请不要重复使用信号量。

答案 2 :(得分:1)

我能够编写类似于我认为你正在寻找的东西,它似乎按照你想要的方式工作(但同样,我不是100%肯定我明白你在寻找什么。):

<强> 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

...然后是调用代码:

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");

希望这有帮助。