4

I am doing multithread programming in C++ and I am wondering whether there is a thread-safe implementation of ringbuffer in C++ or do you have any idea how I can implement it.

Avb Avb
  • 545
  • 2
  • 13
  • 25
  • Maybe http://stackoverflow.com/questions/9743605/thread-safe-implementation-of-circular-buffer answers your question – villekulla Sep 27 '13 at 20:17
  • 1
    Are you looking for the special case when only one thread writes and another thread reads? Or do you want a general solution? – paddy Sep 27 '13 at 20:18
  • 1
    Implementing any thread safe container is relatively easy, unless of course performance is your driving factor. What exactly is it that you're looking for? – Chad Sep 27 '13 at 20:20
  • I do not know the difference but I am interested with one writing and one reading thread. Performance is important for me. – Avb Avb Sep 27 '13 at 20:20
  • Ring buffers are relatively simple, if incrementing head and tail are atomic, then you're 99% on your way to thread safety. What platform? (Windows/Linux)? Boost available? – Chad Sep 27 '13 at 20:23
  • I am using POSIX threads under linux. I am not using Boost. – Avb Avb Sep 27 '13 at 20:25
  • Is `C++11` available? – Chad Sep 27 '13 at 20:28
  • Read this book, it will help: https://www.amazon.co.uk/C-Concurrency-Action-Practical-Multithreading/dp/1933988770 – Viet May 02 '17 at 21:06
  • http://www.boost.org/doc/libs/1_61_0/doc/html/boost/lockfree/spsc_queue.html is probably what you're looking for – Evgeny Shavlyugin Aug 26 '16 at 04:45

1 Answers1

-1

Here's a basic implementation. Requires objects stored in the buffer to be default constructable, and copyable (by storing them in a std::vector<>). Requires C++11 support (for std::atomic). Most any recent version of gcc will have it with -std=c++11 or -std=c++0x

If c++11 isn't available, substitute the appropriate compiler intrinsic for making head_ and tail_ atomic.

Should be safe for one reader thread and one writer thread.

Publish items by calling:

  auto val = ringbuffer.back();
  val = some_value;
  ringbuffer.push();

Retreive items by calling:

  auto val = ringbuffer.front();
  // do stuff with val
  ringbuffer.pop();

If back() returns a nullptr, then the buffer is "full". If front() returns a nullptr then the buffer is "empty".

Warning, not tested (at all) :D

  #include <vector>

  template <class T>
  class RingBuffer
  {
  public:
     RingBuffer(size_t buffer_size)
        : ring_(buffer_size)
        , buffer_size_(buffer_size)
        , head_(0)
        , tail_(0)
     {
     }

     T* back()
     {
        bool received = false;

        if(available(head_, tail_))
        {
           return &(ring_[head_ % buffer_size_]);
        }

        return nullptr;
     }

     void push()
     {
        ++head_;
     }

     T* front()
     {
        if(tail_ < head_)
        {
           return & ring_[tail_ % buffer_size_];
        }

        return nullptr;
     }

     void pop()
     {
        ++tail_;
     }

     size_t size() const
     {
        if(tail_ < head_)
           return buffer_size_ - ((tail_ + buffer_size_) - head_);
        else if(tail_ > head_)
           return buffer_size_ - (tail_ - head_);

        return 0;
     }

     bool available()
     {
        return available(head_, tail_);
     }

  private:
     bool available(uint64_t h, uint64_t t) const
     {
        if(h == t)
           return true;
        else if(t > h)
           return (t - h) > buffer_size_;
        else// if(h > t)
           return (t + buffer_size_) - h > 0;
     }

     std::vector<T> ring_;
     const size_t   buffer_size_;
     std::atomic<uint64_t> head_;
     std::atomic<uint64_t> tail_;
  };
Chad
  • 18,706
  • 4
  • 46
  • 63
  • I did not understand what you mean by "If c++11 isn't available, substitute the appropriate compiler intrinsic for making head_ and tail_ atomic." – Avb Avb Sep 27 '13 at 20:51
  • `head_` and `tail_` need to be atomic, meaning that modifying (or reading their values) should be an atomic operation. If you don't understand what that means, then I think you're probably not ready for multithreaded programming. – Chad Sep 28 '13 at 13:26
  • Hmmm. Not sure I get this. Isn't eg `size` vulnerable to `push` being called from another thread and changing the value of `head_` between the `if` and `return`? – Tom Mar 31 '15 at 01:58
  • Size is correct at the time of the entry (to that function) from one thread. Two subsequent calls to size may return different values. It is untested though. – Chad Mar 31 '15 at 04:32
  • 2
    I don't think this is threadsafe at all as you could call front() or back() at any point in time, hang onto the pointer and modify the underlying data. To make it thread safe I think you would either need to provide "push(somedata)/pop" and wrap access to ring_ in a mutex internally or wrap all access to this class in a mutex externally. I'm no expert, but I don't think the atomics are really doing anything to guard the data itself currently. – pilkch Dec 21 '15 at 21:48