【发布时间】:2014-07-03 04:22:40
【问题描述】:
我在 $work 有一个应用程序,我必须在两个按不同频率调度的实时线程之间移动。 (实际的调度超出了我的控制。)应用程序是硬实时的(其中一个线程必须驱动硬件接口),因此线程之间的数据传输应该是无锁和无等待的尽可能。
需要注意的是,只需要传输一个块数据:因为两个线程运行的速率不同,在慢速线程的两次唤醒之间会有两次快线程迭代完成的时候;在这种情况下,可以覆盖写入缓冲区中的数据,以便较慢的线程仅获取最新数据。
换句话说,代替队列,双缓冲解决方案就足够了。这两个缓冲区是在初始化期间分配的,读写线程可以调用该类的方法来获取指向其中一个缓冲区的指针。
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 指令?
- 可以做得更好吗?
【问题讨论】:
-
我喜欢这个问题。我下班后看一下,明天某个时候回复你。 (我已经实现了其他无锁数据结构,我几乎可以肯定这可以毫不费力地快速完成。)
-
我也喜欢这个问题。到目前为止似乎是正确的。有有趣的约束,可能允许非常快速的实现。我也会有这个用处。但我还不确定一个好的(更好的)实现。
-
为什么不使用循环缓冲区?我想这会简化获取最新数据的问题。
-
好吧...这比我想象的要复杂 :-) 我正在执行一个实现,会及时通知您。
-
@Chad:如果您使用显式获取和释放内存屏障而不是默认的顺序一致的内存屏障,您将获得更好的性能(在 x86 上要好得多)。此外,
%会减慢速度——考虑将大小强制为 2 的幂,以便您可以改用&。 Facebook'sfollyqueue,顺便说一句,具有相同的语义,但我提到的所有改进(他们使用if而不是%并依赖于分支预测):-)
标签: c++ concurrency real-time producer-consumer double-buffering