2010-01-12 4 views
2

스레드 안전 로거를 디자인해야합니다. 내 로거에는 단순히 기록 할 텍스트를 대기시키는 Log() 메서드가 있어야합니다. 또한 로거는 잠금이 없어야하므로 다른 스레드가 로거를 잠그지 않고 메시지를 기록 할 수 있습니다. 일부 동기화 이벤트에 대해 을 대기해야하는 작업자 스레드를 디자인하고 표준 .NET 로깅 (스레드로부터 안전하지 않은)을 사용하여 큐에서 모든 메시지를 기록해야합니다. 그래서 내가 관심있는 것은 작업자 스레드와 로그 기능의 동기화이다. 아래는 내가 디자인 한 클래스의 스케치입니다. Monitor.Wait/Pulse를 여기 또는 다른 방법으로 사용하여 작업자 스레드를 일시 중지했다가 다시 시작해야한다고 생각합니다. 로거 작업이 없을 때 CPU주기를 소비하고 싶지 않습니다.잠금없는 스레드 안전 큐 - 조언이 필요합니다

다른 방법으로 말하자면 - 이 아닌 로거를 디자인하고 싶습니다. 사용하는 호출자 스레드는입니다. 필자는 고성능 시스템을 갖추고 있으며 이는 필수 요건입니다.

class MyLogger 
{ 
    // This is a lockfree queue - threads can directly enqueue and dequeue 
    private LockFreeQueue<String> _logQueue; 
    // worker thread 
    Thread _workerThread; 
    bool _IsRunning = true; 

// this function is used by other threads to queue log messages 
    public void Log(String text) 
{ 
    _logQueue.Enqueue(text); 
} 

// this is worker thread function 
private void ThreadRoutine() 
{ 
while(IsRunning) 
{ 
    // do something here 
} 
}  
} 

답변

4

"lock-free"는 스레드가 서로를 차단하지 않는다는 의미는 아닙니다. 그것은 매우 효율적이지만 매우 까다로운 메커니즘을 통해 서로를 차단한다는 것을 의미합니다. 매우 고성능 시나리오에만 필요하며 전문가조차도 잘못 받아들입니다 (많이).

"lock-free"를 잊어 버리고 "thread-safe"대기열을 사용하는 것이 가장 좋습니다.

"Blocking Queue"는 this page에서 추천합니다.

ThreadRoutine (소비자)을 클래스 자체에 포함시키는 것이 중요합니다.

질문의 두 번째 부분은 정확히 "일부 동기화 이벤트"가 무엇인지에 달려 있습니다. Method 호출을 사용하려는 경우 원샷 스레드를 시작하도록하십시오. 세마포어를 보다 기다리려면 모니터 및 펄스를 사용하지 마십시오. 그들은 여기서 신뢰할 수 없습니다. AutoResetEvent/ManualResetEvent를 사용하십시오.
표면 처리 방법은 사용 방법에 따라 다릅니다.

귀하의 기본 재료는 다음과 같이한다 : 토론의

class Logger 
{ 
    private AutoResetEvent _waitEvent = new AutoResetEvent(false); 
    private object _locker = new object(); 
    private bool _isRunning = true;  

    public void Log(string msg) 
    { 
     lock(_locker) { _queue.Enqueue(msg); } 
    } 

    public void FlushQueue() 
    { 
     _waitEvent.Set(); 
    } 

    private void WorkerProc(object state) 
    { 
     while (_isRunning) 
     { 
      _waitEvent.WaitOne(); 
      // process queue, 
      // *** 
      while(true) 
      { 
       string s = null; 
       lock(_locker) 
       { 
        if (_queue.IsEmpty) 
         break; 
        s = _queue.Dequeu(); 
       } 
       if (s != null) 
        // process s 
      } 
     } 
    } 
} 

부분은 큐 (*** 표시) 처리 할 때 무엇을 것 같다. 대기열을 잠그고 모든 항목을 처리 할 수 ​​있습니다.이 과정에서 새 항목 추가가 차단되고 (길게) 항목을 하나씩 잠그고 검색하고 매번 잠시 잠급니다. 나는 그 마지막 시나리오를 고수했다.

요약 : 잠금없는 솔루션이 필요하지만 블록 프리 솔루션은 필요하지 않습니다. 블록 프리 (Block-Free)가 존재하지 않는다면, 블로킹이없는 블록을 가능한 한 적게 처리해야합니다. 마지막으로 mys 샘플 (incomplete)을 반복하면 Enqueue와 Dequeue 호출을 잠그는 방법 만 보여준다. 나는 그것이 충분히 빠를 것이라고 생각한다.

+0

여기 내 요구 사항은 고성능 Windows 서비스가 있기 때문에 비 차단 로깅을 구현하는 것입니다. –

+1

하지만 어떻게 로거 스레드에 대한 차단을 구현할 예정입니까? 차단 오버 헤드는 Monitor를 사용하는 것보다 훨씬 큽니다. – wj32

+1

캡틴, 나는 우리가 사물의 명명에 대해서만 동의한다고 생각합니다. –

3

단순한 lock 문을 사용하여 큰 오버 헤드가 발생했다는 것을 프로파일 러에서 확인 했습니까? 자물쇠가없는 프로그래밍은 제대로 진행하기가 매우 어렵습니다. 그리고 정말로 필요한 경우 신뢰할 수있는 소스에서 기존의 것을 취할 것을 제안합니다.

+0

잠금없는 알고리즘은 때때로 잠금을 사용하는 알고리즘보다 더 나은 성능을 제공하지만 잠금 기능을 사용하면 알고리즘을 사용하여 잠금을 사용하는 일부 실패 메커니즘에 대해 잠금없는 알고리즘이 강력하다는 이점이 있습니다. 예를 들어, lock-free 코드를 사용할 때 웨이브 레이드 된 스레드는 경합 된 리소스에 대한 다른 스레드와의 경쟁을 멈추게됩니다.대조적으로, 잠금을 사용할 때 잠금을 획득 한 다음 암호가 걸린 스레드는 다른 스레드가 자원 사용을 무기한 거부하게 할 수 있습니다. – supercat

1

원자 적 조작이 있으면이 잠금을 해제하기가 ​​어렵지 않습니다. 단독으로 링크 된 목록 가져 오기 헤드 포인터가 필요합니다.

로그 기능 :
1. 로그 항목 (로깅 문자열이있는 노드)을 로컬에서 준비하십시오.
2. 로컬 노드의 다음 포인터를 헤드으로 설정하십시오.
3. ATOMIC : 로컬 노드의 주소 머리 교체 동일한 경우가 로컬 노드의 다음과 머리 비교.
4. 작업이 실패하면 2 단계부터 반복하고, 그렇지 않으면 항목이 "대기열"에 있습니다.

근로자 :
1. 헤드을 로컬로 복사하십시오.
2. ATOMIC : NULL로 머리 교체 동일한 경우이 지역 하나 머리 비교.
3. 작업이 실패하면 1 단계부터 반복하십시오.
4. 성공하면 항목을 처리하십시오. 현재 로컬이고 "대기열"에서 빠져 있습니다.

0

this을 살펴보십시오. 그것은 당신이 찾고있는 것 같습니다.