多线程双缓冲区

Rox*_*nne 2 c++ multithreading locking

我有一个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;
};
Run Code Online (Sandbox Code Playgroud)

此类包含两个缓冲区。缓冲区始终通过指针访问:

  • 从中读取的缓冲区,由current指针指定。
  • 写入的缓冲区,由next指针指定。

将有两个线程:更新线程将调用该方法写入下一个缓冲区write,读取线程将调用该方法从当前缓冲区读取read。当更新线程完成时,它调用swap交换缓冲区的指针。

缓冲区的交换必须以原子方式完成,因此我必须在每个方法(创建lk对象)中锁定互斥体。

问题在于,在每个方法中锁定互斥锁会阻止两个线程同时访问其相应的缓冲区。但这两个缓冲区是独立的:如果一个线程修改一个缓冲区,而另一个线程读取另一个缓冲区,则没有问题。

我想允许更新线程在读取线程读取其相应缓冲区的同时修改其缓冲区。有什么办法可以实现这个目标吗?

Jér*_*ard 6

解决问题的一种方法是每个缓冲区使用一个互斥体,并用两者保护您的交换。如果在两个线程之间以同步方式安全地使用交换,您可以安全地删除内部的锁(但在您的代码中似乎并非如此)。

这是一个例子:

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;
};
Run Code Online (Sandbox Code Playgroud)