2013-08-21 2 views
7

작업 완료를 알리는 데 dispatch_semaphore_t를 사용하고 있습니다. 세마포어가 멤버 변수 인 경우 제대로 작동하지 않는 것처럼 보입니다. 내 실제 코드에 설명 된대로 가지고, 나는 중단 점을 넣어/그 코드 내가 얻는 결과를 제공, 그래서dispatch_semaphore_t reuse - 내가 무엇을 놓치고 있습니까?

@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 함수, 작업 함수에서 시작하는 멤버 함수, 멤버 세마포어가 신호를받는 함수 및 대기 후 2 매개 변수

멤버 세마포어를 사용하는 경우 첫 번째 라운드에서 중단 점 "RUN_TEST"에서 멈추고 "START"를 실행하고 중단 점 "SIGNAL"을 실행 한 다음 중단 점을 눌러 실행합니다. "TASK_DONE"- 모두 예상대로입니다.

나는 계속 실행

, 나는, 실행 후 나는 인 세마포어를 사용하여 순서를 실행할 때, 중단 점 "신호"

충돌 중단 "START"를 실행 한 다음 "TASK_NOT_DONE을"중단 점에 명중 멤버, 적절한 시그널/기다림처럼 보입니다. 두 번째로 세마포어를 기다리려고합니다. 나는 그걸 기다렸다가 신호를받습니다.

나는 카운팅 권한 (신호/대기 쌍)을 관리하지 않거나 멤버 세마포어가 신호되지 않은 상태로 돌아 가지 않을 것 같습니다.

내 느낌에는 여기에 실종 된 근본적인 것이 있습니다. 모든 입력을 부탁드립니다.

편집 : 궁극적으로 내가 실종 된 것으로 보이는 것은 실제 코드가 좀 더 복잡하기 때문입니다. 힘든 작업에서 깨끗한 반환 대신에, 관련된 여러 스레드 및 postNotification 있습니다. postNotification을 알림 처리기의 코드로 바꿨습니다. 플래그를 설정하고 세마포 신호를 보냅니다. 이렇게하면 알림 핸들러에서 도입 된 지연이 제거됩니다.

답변

7

예, 이것은 예상되는 동작입니다. 신호를 기다리는 시간이 초과 된 경우 신호가 오면 다음에 번으로 걸려 특정 세마포어를 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 

을, 당신은 클래스 인스턴스 변수를 사용하는 경우, 위, dispatch_semaphore_wait 두 번째 호출이 있기 때문에 발행 된 제 1 신호를 잡을 경우처럼 getSomethingDoneUseMemberSemaphore 동작합니다 (A) 그것은 동일한 세마포어입니다; (b) dispatch_semaphore_signal에 대한 첫 번째 호출 시간이 초과 된 경우.

그러나 매번 고유 한 세마포를 사용하면 dispatch_semaphore_wait에 대한 두 번째 호출이 첫 번째 세마포어의 dispatch_semaphore_signal에 응답하지 않습니다.

+0

무슨 뜻인지 알 겠어. 기본적으로 작업이 대기보다 오래 걸리면 대기 시간이 초과되지만 신호는 계속 카운터를 증가시켜 다음 대기가 즉시 시작됩니다. 그것은 제가 생각한 것입니다. 그러나이 경우 분석기를 연결하여 잠시 후에 작업이 완료되는 것을 볼 수 있습니다. 나는 세마포어가 단지 기다리기 전에 * 신호를 받는지 테스트 할 수 있다고 생각한다. * 나는 오래 전에 프로세스를 시작했다. 시간 초과되지 않으면 계속 진행되고 있으며 대기 시간을 적절하게 조정할 수 있습니다. – GTAE86

+0

@ GTAE86 수정. BTW, 지역 세마포어 코드를 설정 한 방법은 이론적으로 멤버 변수 예제와 동일한 동작을 계속 볼 수 있습니다 (정확한 타이밍의 문제임). 어떤 인스턴스 변수에서 세마포어를 선택하는 것보다 (심지어 현재의 "로컬 변수"예제가 인스턴스 변수에 세마포어 저장을 끝내는) 다양한 작업의 다양한 신호 사이에 혼선을 원하지 않는다면, 세마포어를 매개 변수로 전달하거나 인스턴스 변수를 사용하지 않도록합니다 (또는 각 작업에 대해 객체의 별도 인스턴스를 만듭니다). – Rob

+0

내 코드에서 무슨 일이 벌어지는지를 볼 수 있습니다. 여러 스레드가 실행 중입니다 (예제보다 더 복잡합니다).힘든 과정은 다음과 같습니다 스레드에서 장치에 쓸 대기 스레드 B를 기다립니다. 스레드 C는 응답 (ReadPipeAsync)을 받고 양키 (deque)에 캐시합니다. 스레드 D는 분석을 위해 스레드 D를 뽑습니다. 좋은 경우 결과와 함께 알림을 게시합니다. 알림 처리기는 일부 검사를 수행하고 완료 상태 (잠금으로 보호되는 변수로 설정 됨)를 요청하고 요청 된 경우 세마포어에 신호를 보냅니다. 내 작업이 빨리 완료되었다는 것을 알지만 그 알림은 지연 될 수 있습니다. – GTAE86

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

희망.

+0

@impcc - 답변 해 주셔서 감사합니다. 미안하지만 혼란 스러워요 - 내 디버거는 dispatch_time (DISPATCH_TIME_NOW, (int64_t) (2.5 * NSEC_PER_SEC))은 dispatch_time (DISPATCH_TIME_NOW, (int64_t) 2.5 * NSEC_PER_SEC)과 동일한 값을 반환한다고보고합니다. 내가 놓친 게 있니? 내가하는 일에 대한 당신의 이해까지 - 당신은 옳습니다. 나는 다른 스레드에서 완료하기 위해 하나의 스레드에서 작업을 기다리려고한다. 예를 들어, 10 초 작업이 완료 될 때까지 2.5 초를 기다리는 것처럼 보이므로 대기 시간이 지나면 신호가 발생합니다. 옳은? – GTAE86

+0

나는 당신의 코드를 잘못 읽었으며'(2.5 * NSEC_PER_SEC)'를'dispatch_semaphore_wait'에 직접 넘겨 준다고 생각했다. 내 실수. 예, 첫 번째 대기 후에 신호가 발생합니다. 대기 기능을 두 번 이상 호출하면 각 통화에서 2.5 초 동안 대기합니다. 작업이 완전히 끝날 때까지 기다리려면 DISPATCH_TIME_FOREVER를 사용하십시오. 또는 작업 완료에 대해 비동기 적으로 통보 받고 싶다면 왜'dispatch_group_notify'를 사용하지 않을까요? – ipmcc

+0

주로 GCD를 사용하지 않기 때문에 주로 세마포어를 사용합니다. 내가 읽은 것으로부터, 나는 그것을함으로써 모든 종류의 "규칙"을 깨뜨리고 있을지 모르지만, 그것은 아주 잘 작동하는 것처럼 보인다. 동시성과 스레딩에 대한 모든 Apple 가이드를 읽었으며 가능한 경우 스레드에 대해 권장합니다. 필자는 여러 개의 칩으로 개방형 I/O 세션을 시작해야한다. 기본적으로 칩에 데이터 수집을 시작하고 중지 할 때까지 처리하도록 지시한다. 실제 사용자는 한 번에 몇 시간 동안 실제로 실행할 수 있으므로 명령/제어 및 데이터 수집 스레드는 오랜 시간 동안 실행될 수 있습니다. – GTAE86

2

타임 아웃을 사용하여 dispatch_semaphore_wait를 호출하고 스레드가 여전히 타임 아웃시에 차단되면 dispatch_semaphore_signal이 호출 된 것과 거의 같은 결과가 발생합니다. 하나의 차이점은 dispatch_semaphore_signal이 임의의 스레드를 깨우지 만 시간 초과가이 특정 스레드를 깨우는 것입니다. 다른 차이점은 dispatch_semaphore_wait가 0 대신 0이 아닌 값을 반환한다는 것입니다.

다음과 같은 문제가 있습니다. dispatch_semaphore_signal을 호출 할 사람은 누구나 호출 할 것이고 하나의 신호가 너무 많습니다. 이것은 피하기 어려울 수 있습니다. 10 초의 시간 초과가 있으면 dispatch_semaphore_signal은 10.000000001 초 후에 호출 될 수 있습니다. 그래서 세마포어를 재사용한다면 손에 문제가 생길 수 있습니다.

반면에 세마포어를 재사용하지 않으면 최악의 경우 세마포어 카운트가 1이됩니다.하지만 문제는 없습니다.

요약 : 시간 초과로 기다리는 경우 세마포어를 다시 사용하지 마십시오.

+0

오른쪽. 나는 세마포어 홀더 클래스를 만드는 것을 끝내었고, 작업을 시작할 때 세마포어를 설정하고 클래스 소유의 스레드 안전 컬렉션으로 팝합니다. 그렇게하면 다른 스레드에서 실행중인 완료 코드가이를 확인하고 존재하는 경우이를 알릴 수 있습니다. 위의 경우에 내가 겪었던 주된 문제는 알림 핸들러에서 세마포어에 신호를 보내고 있다는 것이 었습니다. 지연으로 인해 시간이 초과되었습니다. 그래서, 문제를 일으키는 두 가지 일이있었습니다. 의견을 보내 주셔서 감사합니다! – GTAE86