Многопоточный двойной буфер - PullRequest
2 голосов
/ 22 февраля 2020

У меня есть класс Buffer, который реализует шаблон двойного буфера , разработанный с учетом многопоточности:

class Buffer {
public:
   void write(size_t idx, float value) {
      std::lock_guard<std::mutex> lk(mut);
      (*next)[idx] = value; // write to next buffer
   }

   float read(size_t idx) const {
      std::lock_guard<std::mutex> lk(mut);
      return (*current)[idx]; // read from current buffer
   }

   void swap() noexcept {
      std::lock_guard<std::mutex> lk(mut);
      std::swap(current, next); // swap pointers
   }

   Buffer() : buf0(32), buf1(32), current(&buf0), next(&buf1) { }
private:
   std::vector<float> buf0, buf1; // two buffers
   std::vector<float> *current, *next;
   mutable std::mutex mut;
};

Этот класс содержит два буфера. Доступ к буферам всегда осуществляется через указатели:

  • Буфер, в котором выполняется чтение, указывается указателем current.
  • Буфер, в который производится запись, указывается указателем next.

Будет два потока: поток обновления будет записывать в следующий буфер, вызывающий метод write, поток чтения будет читать из текущего буфера, вызывая метод read. Когда поток обновления завершен, он вызывает swap, чтобы поменять указатели на буферы.

Перестановка буферов должна быть сделана атомарно , поэтому мне нужно заблокировать мьютекс в каждом методе (создание объекта lk).

Проблема заключается в том, что блокировка мьютекса в каждом методе не позволяет двум потокам одновременно обращаться к соответствующему буферу. Но два буфера независимы: нет проблем, если один поток изменяет один буфер, а другой поток читает другой буфер.

Я хочу разрешить обновляющему потоку изменять свой буфер в то же время, когда считывающий поток читает свой соответствующий буфер. Есть ли способ добиться этого?

1 Ответ

4 голосов
/ 22 февраля 2020

Одним из способов решения вашей проблемы является использование одного мьютекса на буфер и защита обмена с обоими. Если подкачка безопасно используется синхронно между двумя потоками, вы можете безопасно снять блокировку внутри (однако в вашем коде это не так).

Вот пример:

class Buffer {
public:
   void write(size_t idx, float value) {
      std::lock_guard<std::mutex> lk(mutWrite);
      (*next)[idx] = value; // write to next buffer
   }

   float read(size_t idx) const {
      std::lock_guard<std::mutex> lk(mutRead);
      return (*current)[idx]; // read from current buffer
   }

   void swap() noexcept {
      // Lock both mutexes safely using a deadlock avoidance algorithm
      std::lock(mutWrite, mutRead);
      std::lock_guard<std::mutex> lkWrite(mutWrite, std::adopt_lock);
      std::lock_guard<std::mutex> lkRead(mutRead, std::adopt_lock);

      // In C++17, you can replace the 3 lines above with just the following:
      // std::scoped_lock lk(mutWrite, lkRead);

      std::swap(current, next); // swap pointers
   }

   Buffer() : buf0(32), buf1(32), current(&buf0), next(&buf1) { }
private:
   std::vector<float> buf0, buf1; // two buffers
   std::vector<float> *current, *next;
   mutable std::mutex mutRead;
   std::mutex mutWrite;
};
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...