2013-09-28 5 views
1

전 원형/링 버퍼 방식의 사고 방식이 처음이에요. 이론적으로 어떻게 작동해야하는지에 대한 기사를 읽고이 코드 예제를 생각해 냈습니다. 필자의 시나리오에서는 버퍼에서 여러 스레드 쓰기 및 단일 스레드 읽기를 수행합니다.CircularBuffer, 자물쇠를 추가해야합니까?

쓰기 메서드에 잠금을 추가해야합니까? 사전에

감사합니다!

public class CircularBuffer<T> 
{ 
    private readonly int _size; 

    private int _head; 
    private byte _headMirrorSide; 

    private int _tail; 
    private byte _tailMirrorSide; 

    private readonly T[] _buffer; 

    public CircularBuffer() : this(300) { } 

    public CircularBuffer(int size) 
    { 
     _size = size; 
     _buffer = new T[_size + 1]; 

     _head = 0; 
     _headMirrorSide = 0; 

     _tail = 0; 
     _tailMirrorSide = 0; 
    } 

    private bool IsFull() 
    { 
     return _tail == _head && _tailMirrorSide != _headMirrorSide; 
    } 

    public bool IsEmpty() 
    { 
     return _tail == _head && _tailMirrorSide == _headMirrorSide; 
    } 

    private void MovePointer(ref int pointer, ref byte mirrorSide) 
    { 
     pointer = pointer + 1; 

     if (pointer == _size) 
     { 
      mirrorSide ^= 1; 
      pointer = 0; 
     } 
    } 

    public void Write(T obj) 
    { 
     _buffer[_head] = obj; 

     if (IsFull()) 
     { 
      MovePointer(ref _tail, ref _tailMirrorSide); 
     } 
     MovePointer(ref _head, ref _headMirrorSide); 
    } 

    public T Read() 
    { 
     var obj = _buffer[_tail]; 
     _buffer[_tail] = default(T); 

     MovePointer(ref _tail, ref _tailMirrorSide); 

     return obj; 
    } 
} 

편집 : 최종 결과는 이와 유사합니다. 당신이 읽기 전용 버퍼 그들 모두를 액세스하는 여러 스레드가있는 경우

public class CircularBuffer<T> where T : class 
{ 
    private readonly int _size; 

    private int _head; 
    private byte _headMirrorSide; 

    private int _tail; 
    private byte _tailMirrorSide; 

    private readonly T[] _buffer; 

    private readonly object _lock = new object(); 

    public CircularBuffer() : this(300) { } 

    public CircularBuffer(int size) 
    { 
     _size = size; 
     _buffer = new T[_size + 1]; 

     _head = 0; 
     _headMirrorSide = 0; 

     _tail = 0; 
     _tailMirrorSide = 0; 
    } 

    private bool IsFull() 
    { 
     return _tail == _head && _tailMirrorSide != _headMirrorSide; 
    } 

    private bool IsEmpty() 
    { 
     return _tail == _head && _tailMirrorSide == _headMirrorSide; 
    } 

    private void MovePointer(ref int pointer, ref byte mirrorSide) 
    { 
     pointer = pointer + 1; 

     if (pointer == _size) 
     { 
      mirrorSide ^= 1; 
      pointer = 0; 
     } 
    } 

    public void Write(T obj) 
    { 
     lock (_lock) 
     { 
      _buffer[_head] = obj; 

      if (IsFull()) 
      { 
       MovePointer(ref _tail, ref _tailMirrorSide); 
      } 
      MovePointer(ref _head, ref _headMirrorSide); 
     } 
    } 

    public T Read() 
    { 
     lock (_lock) 
     { 
      if (IsEmpty()) 
      { 
       return null; 
      } 

      var obj = _buffer[_tail]; 
      MovePointer(ref _tail, ref _tailMirrorSide); 

      return obj; 
     } 
    } 
} 
+1

순환 버퍼를 스레드 안전하고 대기 상태로 만드는 것은 프로듀서 1 명과 소비자 1 명이 쉽게 수행 할 수 있습니다. 그러나이 시나리오에서는 코드가 안전하지 않습니다. 여러 작성자를 지원하는 것은 훨씬 더 복잡합니다. –

+0

정말로 필요한 것이 무엇인지, 그리고 왜 ConcurrentQueue가 아닌지에 대한 질문에 집중하십시오. –

+0

제한에 도달하면 "이전"값을 덮어 쓰는 기능이있는 제한된 FIFO 목록이 필요합니다. 링 버퍼가 이러한 요구 사항을 아주 잘 만족시켜야하지 않습니까? – mckn

답변

1

다른 스레드의 데이터를 다루는 한, 액세스를 동기화해야합니다. 가장 간단한 방법은 lock() 명령이며 Read() 및 Write() 메서드에 모두 배치해야합니다.

분명히 Write()는 동일한 메모리 셀에 동시 제출을 피하기 위해 lock()이 있어야합니다 (버퍼가 "winner"가 아닌 모든 작성자의 데이터를 누적하기 때문에).

읽기()

  • 은 쓰기와 같은 내부 구성원()를 수행
  • 이 컴파일러 최적화뿐만 아니라 런타임 명령을 다시 순서에 의해 영향을받을 수를 수정하기 때문에뿐만 아니라 잠금()해야한다

PS (전산 효과가 동일하지만 때문에 스레드 간 상호 작용이 파괴 될 수있다) 고급 수준에서는 단방향 잠금 대신 단방향 메모리 장벽을 사용할 수 있지만 많은 경험이 필요합니다.

1

는, 당신은 잠금을 사용할 필요가 없습니다. 그러나 하나 이상의 스레드가 데이터를 수정하는 경우 잠금이 필요합니다. 따라서 귀하의 경우 Read() 메서드는 데이터 (버퍼 포인터의 위치)도 변경하므로 명확한 답변은 명확합니다. YES입니다.

+0

실제로 "쓰기 기능에 잠금을 추가해야합니까?"라는 질문에 대한 대답은 "아니오"입니다. 그는 분명히 또한 읽을 거리가 필요하기 때문에) – Voo