Roxanne
Roxanne

Reputation: 65

Multithreaded double buffer

I have a Buffer class that implements the double buffer pattern designed with multithreading in mind:

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;
};

This class contains two buffers. The buffers are always accessed through pointers:

There will be two threads: an updating thread will write to the next buffer calling the write method, a reading thread will read from the current buffer calling the read method. When the updating thread is done it calls swap to swap the pointers to the buffers.

The swapping of the buffers must be done atomically so I have to lock the mutex in every method (creating the lk object).

The problem is that locking the mutex in every method prevents the two threads from accessing their corresponding buffer at the same time. But the two buffers are independent: there is no problem if one thread modifies one buffer while the other thread reads the other buffer.

I want to allow the updating thread to modify its buffer at the same time the reading thread reads its corresponding buffer. Any way to achieve this?

Upvotes: 2

Views: 5824

Answers (2)

cho EEE
cho EEE

Reputation: 1

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

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

   
   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;
};

At one time, it can only happen write or read function (if only use one mutex).

Upvotes: 0

J&#233;r&#244;me Richard
J&#233;r&#244;me Richard

Reputation: 50358

One way to solve your problem is by using one mutex per buffer and protect your swap with both. If the swap is safely used in a synchronous way between the two threads, you can safely remove the lock inside (however it seems to not be the case in your code).

Here is an example:

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, mutRead );

      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;
};

Upvotes: 6

Related Questions