1

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

我是否需要为 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;
        }
    }
}
4

2 回答 2

1

As far as you're touching the data from different threads, you have to synchronize the access. The simplest way is lock() instruction, and it should be placed at both Read() and Write() methods.

Obviously, Write() should has lock() to avoid concurrent submit into the same memory cell (because the buffer is accumulating the data from every writer, not "winner" only).

Read() should have lock() as well because it

  • modifies same internal members as Write() does
  • can be affected by compiler optimization as well as run-time instruction re-ordering (because computational effect is the same, but inter-thread interaction may be ruined)

P.S. At advanced level, you may use one-way memory barriers instead of unidirectional locks, but this requires a lot of experience

于 2013-09-28T12:28:35.200 回答
1

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

于 2013-09-28T12:24:34.617 回答