Можно ли реализовать потокобезопасный циклический буфер, состоящий из массивов?

Я пытаюсь реализовать циклический буфер, который использует мьютекс, чтобы быть потокобезопасным. Я использовал следующий код:

#include <cstdio>

#include <memory>
#include <mutex>

template <class T>
class circular_buffer {
public:
    explicit circular_buffer(size_t size) :
        buf_(std::unique_ptr<T[]>(new T[size])),
        max_size_(size)
    {

    }

    void put(T item)
    {
        std::lock_guard<std::mutex> lock(mutex_);

        buf_[head_] = item;

        if (full_)
        {
            tail_ = (tail_ + 1) % max_size_;
        }

        head_ = (head_ + 1) % max_size_;

        full_ = head_ == tail_;
    }

    T get()
    {
        std::lock_guard<std::mutex> lock(mutex_);

        if (empty())
        {
            return T();
        }

        //Read data and advance the tail (we now have a free space)
        auto val = buf_[tail_];
        full_ = false;
        tail_ = (tail_ + 1) % max_size_;

        return val;
    }

    void reset()
    {
        std::lock_guard<std::mutex> lock(mutex_);
        head_ = tail_;
        full_ = false;
    }

    bool empty() const
    {
        //if head and tail are equal, we are empty
        return (!full_ && (head_ == tail_));
    }

    bool full() const
    {
        //If tail is ahead the head by 1, we are full
        return full_;
    }

    size_t capacity() const
    {
        return max_size_;
    }

    size_t size() const
    {
        size_t size = max_size_;

        if (!full_)
        {
            if (head_ >= tail_)
            {
                size = head_ - tail_;
            }
            else
            {
                size = max_size_ + head_ - tail_;
            }
        }

        return size;
    }

private:
    std::mutex mutex_;
    std::unique_ptr<T[]> buf_;
    size_t head_ = 0;
    size_t tail_ = 0;
    const size_t max_size_;
    bool full_ = 0;
};

Проблема с этим кодом в том, что я не могу заставить его работать с массивами поплавков. Я получаю, что функция возвращает ошибку массива (из функции получения). Я не совсем уверен, как это исправить (пробовал передать массив и использовать функцию get() для указания этого массива, но это тоже не сработало). Извините, если этот вопрос немного абстрактен, я, честно говоря, полностью запутался в этом вопросе (первая работа в качестве разработчика и буквально мои 6 дней работы, они заставили меня сделать очень сложное приложение для радиолокационной карты). Дайте мне знать, если вам нужны какие-либо разъяснения по чему-либо.

редактировать: Спасибо всем! Ответ Майкла сработал, и спасибо за предложения. Честно говоря, я чувствую, что прямо сейчас тону над головой, поэтому все советы чрезвычайно полезны!


person cheesemas46    schedule 08.05.2019    source источник
comment
Конечно, это возможно. Лучше объясните, что не работает с вашей реализацией, чтобы получить помощь.   -  person πάντα ῥεῖ    schedule 09.05.2019
comment
Это кажется разумным (и на самом деле очень хорошим усилием для тех, кто уже 6 дней в своей карьере). Есть несколько проблем - у вас есть некоторые общедоступные функции-члены (size, empty, full), которые также требуют блокировки - вы не можете получить доступ к этим переменным потокобезопасным способом без блокировки (или каким-то образом сделать их атомарными). Помните, когда вы добавляете блокировки, что вызов get к empty не должен блокироваться во второй раз - вам, вероятно, нужен частный empty_no_lock(), который вызывает get, который не добавляет блокировку.   -  person Mike Vine    schedule 09.05.2019
comment
@MikeVine не должен блокировать второй раз ... или рекурсивную блокировку   -  person curiousguy    schedule 10.05.2019


Ответы (1)


Прежде всего, имейте в виду, что если кто-то использует методы size(), empty() или full() экземпляра этого шаблона класса, в то время как кто-то другой одновременно использует get(), put() или reset(), вы получите неопределенное поведение. size() или empty() также должны будут заблокировать мьютекс, потому что они считывают значения объектов (full_, head_ и tail_), которые потенциально могут быть изменены одновременно. Кроме того, мне кажется, что put() всегда что-то пишет, даже если очередь заполнена. Это, вероятно, не то, что обычно хотелось бы.

Основываясь на вашем описании, я предполагаю, что проблема, о которой вы спрашиваете, связана с попыткой создать, например, файл circular_buffer<float[4]>. Подумайте, во что превратится метод get(), если вы замените тип float[4] на T:

float get()[4] { … }

В итоге вы получите функцию, возвращающую массив. Функциям не разрешено возвращать массивы [dcl.fct]/11. * Вот почему вы получаете ошибку компилятора, как только вызываете метод get() для такого circular_buffer. Используйте, например, std::array вместо: circular_buffer<std::array<float, 4>>.

*) Я полагаю, что это, скорее всего, по историческим причинам. Способ, которым типы массивов должны вести себя при передаче функциям в C, был таким, что массивы в конечном итоге фактически передавались по ссылке; у функции нет хорошего способа вернуть массив по ссылке, а возврат по значению будет несовместим с тем, как они передаются. Таким образом, вероятно, лучше просто запретить возврат массивов вообще

person Michael Kenzel    schedule 08.05.2019
comment
Отслеживание _full необходимо, если вы хотите отличить полный буфер от пустого - в обоих случаях голова == хвост. То, что он сделал, работает (хотя это довольно противно читать. - person Mike Vine; 09.05.2019
comment
@MikeVine хорошее замечание, я должен был рассказать об этом немного больше. исправлено, еще раз спасибо! - person Michael Kenzel; 09.05.2019
comment
@SolomonSlow Существует неявное преобразование массива в указатель [conv.array]/1, но это означает только то, что массив может быть преобразован, а не то, что массивы всегда преобразуются. Причина, по которой массивы эффективно передаются по ссылке, заключается в [dcl.fct]/5< /а>. Если параметр функции объявлен как тип массива, тип параметра неявно корректируется, чтобы вместо этого быть типом указателя. Обратите внимание, что это влияет на тип самой функции, это не имеет ничего общего с тем, является ли аргумент именем массива или нет… - person Michael Kenzel; 09.05.2019
comment
!! Думаю, моя проблема в том, что я до сих пор продолжаю думать о C++ как о C с дополнительными функциями. Я должен сломать эту привычку !! - person Solomon Slow; 10.05.2019
comment
@SolomonSlow в данном случае, в C все точно так же… ;) - person Michael Kenzel; 10.05.2019