简体   繁体   中英

Is my circular buffer threadsafe? If not, how can I make it so?

I have created a circular buffer class and need to access it from two different threads. The circular buffer uses a 2 dimensional array with one dimension the number of rows and the other the elements of an array of floats (2048 of them). The user interface thread can read from the arrays, all rows at any given time. The background thread, is a TCP server thread that retrieves 2048 floats that need to be inserted in this array. Here is the code

    static class CircularArrayBuffer
    {

    static float[,] buffer;
    static int columns, rows;
    static int nextFree = 0;

    public static void CreateBuffer(int _columns, int _rows)
    {
        columns = _columns;
        rows = _rows;

        buffer = new float[rows,columns];
        nextFree = 0;       //reset pointer to first free buffer
    }

    public static float[] GetData(int index)
    {
        if (index > rows)
        {
            throw new System.ArgumentException("Index cannot be more than rows", "index");
        }

        float[] rowArray = new float[columns];

        Buffer.BlockCopy(buffer, (((nextFree - 1 + index) % rows) * 4 * columns), rowArray, 0, columns * 4); //takes 2 microseconds!

        return rowArray;
    }       

    public static void  AddData(float[] rowArray) //number of columns must be set!
    {
        if (rowArray.Count() > columns)
        {
            throw new System.ArgumentException("Data length cannot be more than number of columns", "columns");
        }

        Buffer.BlockCopy(rowArray, 0, buffer, nextFree * 4 * columns, columns * 4);
        nextFree = (nextFree + 1) % rows;
    }
}

So the user interface thread will be retrieving all rows every 50ms or so while the background TCP server will be adding 1 row every 50 ms or so. The user interface thread is actually a the OnRender callback of OpenGL. Am I going to run into issues with this class? If so how to I avoid it? Thanks, Tom

There is always a chance of getting inconsistent values if the write happens in the middle of GetData(). You probably want to use locks to make the Getdata and Adddata operations atomic to get consistent values. Also on the side note, its probably fine right now, but since its worth mentioning, its always good to make the Get call asynchronous to avoid freezing the UI thread in case it doesnt get the lock immediately.

It doesn't look thread safe to me. You may wish to try something like this:

static class CircularArrayBuffer
{

    static float[,] buffer;
    static int columns, rows;
    static int nextFree = 0;
    static readonly ReaderWriterLockSlim rwLockSlim = new ReaderWriterLockSlim();

    public static void CreateBuffer(int _columns, int _rows)
    {
        columns = _columns;
        rows = _rows;

        buffer = new float[rows, columns];
        nextFree = 0;       //reset pointer to first free buffer
    }

    public static float[] GetData(int index)
    {
        try
        {
            rwLockSlim.EnterReadLock();
            if (index > rows)
            {
                throw new System.ArgumentException("Index cannot be more than rows", "index");
            }

            float[] rowArray = new float[columns];

            Buffer.BlockCopy(buffer, (((nextFree - 1 + index) % rows) * 4 * columns), rowArray, 0, columns * 4); //takes 2 microseconds!
        }
        catch(Exception ex)
        {
            //handle the exception nicely
        }
        finally
        {
            rwLockSlim.ExitReadLock();
        }
        return rowArray;
    }

    public static void AddData(float[] rowArray) //number of columns must be set!
    {
        try
        {
            rwLockSlim.EnterWriteLock();
            if (rowArray.Count() > columns)
            {
                throw new System.ArgumentException("Data length cannot be more than number of columns", "columns");
            }

            Buffer.BlockCopy(rowArray, 0, buffer, nextFree * 4 * columns, columns * 4);
            nextFree = (nextFree + 1) % rows;
        }
        catch(Exception ex)
        {
            //handle the exception nicely
        }
        finally
        {
            rwLockSlim.ExitWriteLock();
        }
    }
}

And here's why

  • there is nothing stopping different threads modifying nextFree from AddData while thread in GetData is trying to use it
  • multiple threads can access AddData and modify nextFree so the Buffer.BlockCopy will put data at a wrong index

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM