【发布时间】:2015-07-14 22:26:44
【问题描述】:
常规的std::vector 有emplace_back,可以避免不必要的复制。 spsc_queue 不支持这个有什么原因吗?是否因为某种原因无法使用无锁队列进行emplace?
【问题讨论】:
标签: boost thread-safety locking emplace
常规的std::vector 有emplace_back,可以避免不必要的复制。 spsc_queue 不支持这个有什么原因吗?是否因为某种原因无法使用无锁队列进行emplace?
【问题讨论】:
标签: boost thread-safety locking emplace
我既不是 boost 库的实现者也不是维护者,所以我不知道为什么不包含 emplace 成员函数的理由,但如果你真的需要它,自己实现它并不太难。
spsc_queue 具有compile_time_sized_ringbuffer 或runtime_sized_ringbuffer 的基类,具体取决于在编译时是否知道队列的大小。这两个类维护使用的实际缓冲区,动态缓冲区和编译时缓冲区之间存在明显差异,但在这种情况下,将它们的 push 成员函数委托给一个公共基类 - ringbuffer_base。
ringbuffer_base::push 函数比较容易理解:
bool push(T const & t, T * buffer, size_t max_size)
{
const size_t write_index = write_index_.load(memory_order_relaxed); // only written from push thread
const size_t next = next_index(write_index, max_size);
if (next == read_index_.load(memory_order_acquire))
return false; /* ringbuffer is full */
new (buffer + write_index) T(t); // copy-construct
write_index_.store(next, memory_order_release);
return true;
}
通过relaxed 加载(这是安全的,因为此类的预期用途是push 调用的单个生产者)并获取适当的下一个索引, 检查以确保所有内容都在边界内(通过加载获取以与调用 pop 的线程进行适当同步),但我们感兴趣的主要语句是:
new (buffer + write_index) T(t); // copy-construct
它执行将新副本构造放置到缓冲区中。传递一些参数以用于直接从可行的构造函数参数构造T,这在本质上是线程不安全的。我编写了以下 sn-p 并在派生类中进行了必要的更改,以适当地将工作委托给基类:
template<typename ... Args>
std::enable_if_t<std::is_constructible<T,Args...>::value,bool>
emplace( T * buffer, size_t max_size,Args&&... args)
{
const size_t write_index = write_index_.load(memory_order_relaxed); // only written from push thread
const size_t next = next_index(write_index, max_size);
if (next == read_index_.load(memory_order_acquire))
return false; /* ringbuffer is full */
new (buffer + write_index) T(std::forward<Args>(args)...); // emplace
write_index_.store(next, memory_order_release);
return true;
}
也许唯一的区别是确保在Args... 中传递的参数实际上可以用于构造T,当然也可以通过std::forward 来代替复制构造。
【讨论】: