CircularBuffer,我需要添加锁吗?

时间:2013-09-28 12:09:36

标签: c# .net multithreading circular-buffer

我是整个循环/环缓冲思维方式的新手。我在理论上阅读了一些关于它应该如何工作的文章,并提出了这个代码示例。在我的场景中,我将有多个线程写入,并从缓冲区读取一个线程。

我是否需要为write方法添加锁定?

提前致谢!

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;
        }
    }
}

2 个答案:

答案 0 :(得分:1)

如果您有多个线程访问缓冲区并且所有这些线程只能读取,那么您不必使用锁定。但是,如果一个或多个线程正在修改数据,那么您将需要一个锁。所以在你的情况下,答案是明确的,明确的 YES ,特别是因为你的Read()方法也改变了数据:缓冲区指针的位置。

答案 1 :(得分:1)

只要您触摸来自不同线程的数据,就必须同步访问权限。最简单的方法是lock()指令,它应该放在Read()和Write()方法中。

显然,Write()应该有lock()以避免并发提交到同一个内存单元格(因为缓冲区正在累积来自每个编写器的数据,而不是“优胜者”)。

Read()也应该有lock(),因为它

  • 修改与Write()相同的内部成员
  • 可能受编译器优化以及运行时指令重新排序的影响(因为计算效果相同,但线程间交互可能会被破坏)

P.S。在高级别,您可以使用单向内存屏障而不是单向锁,但这需要很多经验