需要注意的是,只需要传输一个数据块:由于两个线程以不同的速率运行,较快的线程在较慢的线程唤醒之间完成两次迭代,因此在这种情况下,覆盖写缓冲区中的数据以使较慢的线程仅获取最新数据是可以的。
换句话说,双缓冲解决方案就足够了,而不是队列。这两个缓冲区在初始化期间分配,并且读取线程和写入线程可以调用该类的方法来获取其中一个缓冲区的指针。
C++代码:
#include <mutex>
template <typename T>
class ProducerConsumerDoubleBuffer {
public:
ProducerConsumerDoubleBuffer() {
m_write_busy = false;
m_read_idx = m_write_idx = 0;
}
~ProducerConsumerDoubleBuffer() { }
// The writer thread using this class must call
// start_writing() at the start of its iteration
// before doing anything else to get the pointer
// to the current write buffer.
T * start_writing(void) {
std::lock_guard<std::mutex> lock(m_mutex);
m_write_busy = true;
m_write_idx = 1 - m_read_idx;
return &m_buf[m_write_idx];
}
// The writer thread must call end_writing()
// as the last thing it does
// to release the write busy flag.
void end_writing(void) {
std::lock_guard<std::mutex> lock(m_mutex);
m_write_busy = false;
}
// The reader thread must call start_reading()
// at the start of its iteration to get the pointer
// to the current read buffer.
// If the write thread is not active at this time,
// the read buffer pointer will be set to the
// (previous) write buffer - so the reader gets the latest data.
// If the write buffer is busy, the read pointer is not changed.
// In this case the read buffer may contain stale data,
// it is up to the user to deal with this case.
T * start_reading(void) {
std::lock_guard<std::mutex> lock(m_mutex);
if (!m_write_busy) {
m_read_idx = m_write_idx;
}
return &m_buf[m_read_idx];
}
// The reader thread must call end_reading()
// at the end of its iteration.
void end_reading(void) {
std::lock_guard<std::mutex> lock(m_mutex);
m_read_idx = m_write_idx;
}
private:
T m_buf[2];
bool m_write_busy;
unsigned int m_read_idx, m_write_idx;
std::mutex m_mutex;
};
为了避免读取线程中的旧数据,负载结构被版本化。为了方便线程之间的双向数据传输,使用了上述两个实例,它们是相反的。
问题:
- 这个方案线程安全吗?如果有问题,出在哪里?
- 能否在没有互斥锁的情况下完成?也许只需要内存屏障或CAS指令?
- 能否做得更好?
%
会减慢速度-考虑将大小强制为2的幂次方,这样您就可以使用&
。顺便说一句,Facebook的folly
队列具有相同的语义,但我提到的所有改进都有(他们使用if
而不是%
并依赖于分支预测) :-) - Cameron