2009-09-10 2 views
3

나는 다음과 같은 코드를 가지고 :구함 : 우아한 해결책은 상태 경주

class TimeOutException 
{}; 

template <typename T> 
class MultiThreadedBuffer 
{ 
public: 
    MultiThreadedBuffer() 
    { 
     InitializeCriticalSection(&m_csBuffer); 
     m_evtDataAvail = CreateEvent(NULL, TRUE, FALSE, NULL); 
    } 
    ~MultiThreadedBuffer() 
    { 
     CloseHandle(m_evtDataAvail); 
     DeleteCriticalSection(&m_csBuffer); 
    } 
    void LockBuffer() 
    { 
     EnterCriticalSection(&m_csBuffer); 
    } 
    void UnlockBuffer() 
    { 
     LeaveCriticalSection(&m_csBuffer); 
    } 
    void Add(T val) 
    { 
     LockBuffer(); 
     m_buffer.push_back(val); 
     SetEvent(m_evtDataAvail); 
     UnlockBuffer(); 
    } 
    T Get(DWORD timeout) 
    { 
     T val; 
     if (WaitForSingleObject(m_evtDataAvail, timeout) == WAIT_OBJECT_0) { 
      LockBuffer(); 

      if (!m_buffer.empty()) { 
       val = m_buffer.front(); 
       m_buffer.pop_front(); 
      } 

      if (m_buffer.empty()) { 
       ResetEvent(m_evtDataAvail); 
      } 

      UnlockBuffer(); 
     } else { 
      throw TimeOutException(); 
     } 
     return val; 
    } 
    bool IsDataAvail() 
    { 
     return (WaitForSingleObject(m_evtDataAvail, 0) == WAIT_OBJECT_0); 
    } 
    std::list<T> m_buffer; 
    CRITICAL_SECTION m_csBuffer; 
    HANDLE m_evtDataAvail; 
}; 

단위 테스트는 T의 기본 생성자만큼 하나의 스레드에서 사용/대입 연산자가 '돈 복사 할 때이 코드가 잘 작동하는지 보여줍니다 던지기. 나는 T를 쓰고 있기 때문에 받아 들일 만하다.

내 문제는 Get 메서드입니다. 사용 가능한 데이터가 없으면 (즉, m_evtDataAvail이 설정되지 않은 경우) 몇 개의 스레드가 WaitForSingleObject 호출을 차단할 수 있습니다. 새 데이터를 사용할 수있게되면 모두 Lock() 호출로 넘어갑니다. 하나만 통과하고 데이터를 가져와 계속 진행할 수 있습니다. Unlock()을 한 후 다른 스레드가 계속 이동할 수 있으며 데이터가 없다는 것을 알 수 있습니다. 현재는 기본 객체를 반환합니다.

는 내가 일 할 것은의 WaitForSingleObject 통화로 돌아가려면 그 두 번째 스레드 (및 기타)입니다. 잠금을 해제하고 goto 한 블록을 추가 할 수는 있지만 악의를 느낍니다. 솔루션은 또한 제한 시간을 다시 시작할 것입니다 각 여행 이후 다시 무한 루프의 가능성을 추가하는

. 항목에 시계를 확인하고 각 여행에서 시간 초과를 조정하는 코드를 추가 할 수 있지만이 간단한 Get 메서드는 매우 복잡해지기 시작합니다.

테스트 용이성과 단순성을 유지하면서 이러한 문제를 해결하는 방법에 어떤 아이디어?

오, 누구나 궁금 들어, IsDataAvail 기능은 테스트 용으로 만 존재한다. 프로덕션 코드에는 사용되지 않습니다. 추가 및 가져 오기는 비 테스팅 환경에서 사용되는 유일한 방법입니다.

답변

7

수동 재설정 이벤트 대신 자동 재설정 이벤트를 만들어야합니다. 이렇게하면 여러 스레드가 이벤트를 기다리고 있고 이벤트가 설정되면 하나의 스레드 만 릴리스됩니다. 다른 모든 스레드는 대기 상태로 남아 있습니다. CreateEvent API의 두 번째 매개 변수에 FALSE를 전달하여 자동 재설정 이벤트를 만들 수 있습니다. 또한이 코드는 버퍼를 잠그면 예외가 발생하지 않습니다. 즉, 일부 명령문에서 예외가 발생하면 중요한 섹션이 잠금 해제되지 않습니다. RAII 원칙을 사용하면 예외가 발생한 경우에도 중요 섹션의 잠금이 해제되도록 할 수 있습니다.

+0

먼저, 이러한 상황에서 역사적으로 RAII를 사용했습니다. 이 경우 테스트 영역을 늘리지 않기로했습니다. 나는 TDD에 들어가고있다. –

+0

둘째, 기쁘게! if-empty-reset 블록을 if-not-empty-set으로 변경했는데 생각할 수있는 모든 경로가 제대로 처리됩니다. 감사! –

+0

+1, 자동 집계를 사용합니다. 여기에 autoReset을 사용하면 단점이 있습니다. Add 메서드를 사용하여 m_buffer에 여러 항목이 추가 된 경우 스레드가 대기 중이지만 m_buffer에 아직 항목이 표시되지 않은 경우가 있습니다. –

5

일반 Event 객체 대신 Semaphore 객체를 사용할 수 있습니다. 세마포어 수는 0으로 초기화되고 Add가 호출 될 때마다 ReleaseSemaphore로 1 씩 증가해야합니다. 그렇게하면 Get의 WaitForSingleObject는 버퍼의 값보다 버퍼에서 읽는 스레드를 더 이상 릴리스하지 않습니다.

+0

와우. 왼쪽 필드에서 뛰어난 대안이 나온다. 그것은 우아하고 단순합니다. 멋지다. –

+0

+1, 좋은 해결책으로 보입니다. –

3

당신은 항상 이벤트가 신호를하는 경우에 대한 코드를해야하지만, 심지어 자동 리셋 이벤트와, 데이터가없는 것입니다. WaitForsingleevent가 잠에서 깨어 나서 LockBuffer가 호출 될 때까지의 경쟁 조건이 있으며 그 간격에서 다른 스레드가 버퍼의 데이터를 팝 할 수 있습니다. 코드에서 WaitForSingleEvent를 루프에 배치해야합니다. 좀 더 확장 성 및 성능이 좋은 대안 당신을 관심 수도, 대안으로 이미 각 루프 반복에 소요되는 시간과 타임 아웃 ...

감소? Interlocked Singly Linked Lists, OS 스레드 풀 QueueUserWorkItemidempotent 처리. 항목에 pushes을 추가하고 작업 항목을 제출하십시오. 작업 항목 pops이 항목이고 NULL이 아닌 경우 처리합니다. 당신은 환상적으로 움직일 수 있고 프로세서가 반복적으로 루프를 돌리고 추가가 불필요한 작업 항목을 대기 행렬하지 않도록 상태를 '활성'상태로 유지할 수 있지만 엄격하게 요구되는 것은 아닙니다. 더 높은 sclae 및 멀티 코어/멀티 CPU 부하 스프레드의 경우 큐 완료 포트를 사용하는 것이 좋습니다. 자세한 내용은 Rick Vicik의 기사에 설명되어 있으며 한 번에 3 개를 모두 링크하는 블로그 항목이 있습니다 : High Performance Windows programs.

+0

정말 놀리스트의 아이디어가 마음에 들지만, 제가 말할 수있는 한, 그 목록은 LIFO이고 FIFO가 필요합니다. 또는 나는 무엇인가 놓치고 있냐? –

+0

아무 것도 놓치지 않았습니다. 엄격한 FIFO가 시행되기를 당신이 알고 있는지 몰랐습니다. AfIF FIFO 잠금 프리리스트는 LIFO리스트와 같은 단일 연동 교환 오퍼레이션에서 유지 될 수 없기 때문에 사용할 수 없습니다. –