多线程双缓冲区
Multithreaded double buffer
我有一个 Buffer
class 实现了 double buffer pattern 设计时考虑了多线程:
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;
};
此 class 包含两个缓冲区。始终通过指针访问缓冲区:
- 从中读取的缓冲区,由
current
指针指定。
- 写入的缓冲区,由
next
指针指定。
将有两个线程:更新线程将调用 write
方法写入下一个缓冲区,读取线程将调用 read
方法从当前缓冲区读取。更新线程完成后,它调用 swap
交换指向缓冲区的指针。
缓冲区的交换必须以原子方式完成所以我必须在每个方法中锁定互斥体(创建lk
对象)。
问题是在每个方法中锁定互斥锁会阻止两个线程同时访问它们相应的缓冲区。但是这两个缓冲区是独立的:如果一个线程修改一个缓冲区而另一个线程读取另一个缓冲区则没有问题。
我想允许更新线程在读取线程读取其相应缓冲区的同时修改其缓冲区。有什么办法可以做到这一点?
解决您的问题的一种方法是为每个缓冲区使用一个互斥量并保护您与两者的交换。
如果交换在两个线程之间以同步方式安全使用,您可以安全地删除内部的锁(但在您的代码中似乎并非如此)。
这是一个例子:
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;
};
我有一个 Buffer
class 实现了 double buffer pattern 设计时考虑了多线程:
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;
};
此 class 包含两个缓冲区。始终通过指针访问缓冲区:
- 从中读取的缓冲区,由
current
指针指定。 - 写入的缓冲区,由
next
指针指定。
将有两个线程:更新线程将调用 write
方法写入下一个缓冲区,读取线程将调用 read
方法从当前缓冲区读取。更新线程完成后,它调用 swap
交换指向缓冲区的指针。
缓冲区的交换必须以原子方式完成所以我必须在每个方法中锁定互斥体(创建lk
对象)。
问题是在每个方法中锁定互斥锁会阻止两个线程同时访问它们相应的缓冲区。但是这两个缓冲区是独立的:如果一个线程修改一个缓冲区而另一个线程读取另一个缓冲区则没有问题。
我想允许更新线程在读取线程读取其相应缓冲区的同时修改其缓冲区。有什么办法可以做到这一点?
解决您的问题的一种方法是为每个缓冲区使用一个互斥量并保护您与两者的交换。 如果交换在两个线程之间以同步方式安全使用,您可以安全地删除内部的锁(但在您的代码中似乎并非如此)。
这是一个例子:
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;
};