С++ реализация кольцевого буфера с поддержкой потоков

Я занимаюсь многопоточным программированием на С++, и мне интересно, существует ли поточно-безопасная реализация кольцевого буфера на С++, или у вас есть идеи, как я могу это реализовать.


person Avb Avb    schedule 27.09.2013    source источник
comment
Возможно, stackoverflow.com/questions/9743605/ отвечает на ваш вопрос   -  person villekulla    schedule 28.09.2013
comment
Вы ищете особый случай, когда только один поток пишет, а другой читает? Или вы хотите общее решение?   -  person paddy    schedule 28.09.2013
comment
Реализация любого потокобезопасного контейнера относительно проста, если, конечно, производительность не является вашим движущим фактором. Что именно вы ищете?   -  person Chad    schedule 28.09.2013
comment
Я не знаю разницы, но меня интересует одна нить для письма и одна для чтения. Для меня важна производительность.   -  person Avb Avb    schedule 28.09.2013
comment
Кольцевые буферы относительно просты, если увеличение головы и хвоста атомарно, то вы на 99% на пути к безопасности потоков. Какая платформа? (Виндовс/Линукс)? Повышение доступно?   -  person Chad    schedule 28.09.2013
comment
Я использую потоки POSIX под Linux. Я не использую Boost.   -  person Avb Avb    schedule 28.09.2013
comment
Доступен ли C++11?   -  person Chad    schedule 28.09.2013
comment
boost.org/doc/libs/1_61_0/ doc/html/boost/lockfree/, вероятно, то, что вы ищете   -  person Evgeny Shavlyugin    schedule 26.08.2016
comment
Прочтите эту книгу, она поможет: amazon.co. uk/C-Concurrency-Action-Practical-Multhreading/dp/   -  person Viet    schedule 03.05.2017


Ответы (1)


Вот базовая реализация. Требует, чтобы объекты, хранящиеся в буфере, были конструируемыми по умолчанию и копируемыми (сохраняя их в std::vector‹>). Требуется поддержка C++11 (для std::atomic). Почти любая последняя версия gcc будет иметь его с -std=c++11 или -std=c++0x.

Если c++11 недоступен, замените соответствующий встроенный компонент компилятора, чтобы сделать head_ и tail_ атомарными.

Должен быть безопасным для одного потока чтения и одного потока записи.

Опубликуйте элементы, позвонив:

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

Получите товар по телефону:

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

Если back() возвращает nullptr, то буфер "полный". Если front() возвращает nullptr, то буфер "пуст".

Внимание, не тестировалось (вообще) :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_;
  };
person Chad    schedule 27.09.2013
comment
Я не понял, что вы имеете в виду, если С++ 11 недоступен, замените соответствующий встроенный компилятор для того, чтобы сделать head_ и tail_ атомарными. - person Avb Avb; 28.09.2013
comment
head_ и tail_ должны быть атомарными, а это означает, что изменение (или чтение их значений) должно быть атомарной операцией. Если вы не понимаете, что это значит, то я думаю, что вы, вероятно, не готовы к многопоточному программированию. - person Chad; 28.09.2013
comment
Хм. Не уверен, что понял. Например, size не уязвим для вызова push из другого потока и изменения значения head_ между if и return? - person Tom; 31.03.2015
comment
Размер правильный на момент входа (в эту функцию) из одного потока. Два последующих вызова size могут возвращать разные значения. Хотя это не проверено. - person Chad; 31.03.2015
comment
Я не думаю, что это вообще потокобезопасно, так как вы можете вызвать front() или back() в любой момент времени, повесить указатель и изменить базовые данные. Чтобы сделать его потокобезопасным, я думаю, вам нужно либо предоставить push(somedata)/pop и обернуть доступ к ring_ во мьютексе внутри, либо обернуть весь доступ к этому классу во мьютексе извне. Я не эксперт, но я не думаю, что в настоящее время атомщики действительно делают что-то для защиты самих данных. - person pilkch; 22.12.2015