2013-09-28 3 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(), чтобы избежать одновременного представления в одну ячейку памяти (поскольку буфер накапливает данные от каждого автора, а не только от победителя).

Read() должны иметь блокировку(), а также из-за его

  • изменяет то же внутренние элементы, как Write() делает
  • может зависеть от оптимизации компиляторов, а также время выполнения команд повторного заказа (потому что вычислительный эффект один и тот же, но межпоточное взаимодействие может быть разрушено)

PS На продвинутом уровне вы можете использовать односторонние барьеры памяти вместо однонаправленных блокировок, но для этого требуется большой опыт.

1

Если у вас есть несколько потоков к буферу, и все они только для чтения, то вы не должны использовать блокировку. Однако, если один или несколько потоков изменяют данные, вам понадобится блокировка. Так что в вашем случае ответ ясен, окончательный ДА, тем более что ваш метод Read() также изменяет данные: положение указателя буфера.

+0

На самом деле ответ «нет» на вопрос «мне нужно добавить блокировку к функции записи?» Потому что он явно нуждается в чтении;) – Voo

Смежные вопросы