为什么 boost::lockfree::spsc_queue 没有位置?
Why doesn't boost::lockfree::spsc_queue have emplace?
常规 std::vector
有 emplace_back
可以避免不必要的复制。有什么理由 spsc_queue
不支持这个吗?是否由于某种原因无法使用无锁队列emplace
?
我既不是 boost 库的实现者也不是维护者,所以为什么不包含 emplace
成员函数背后的基本原理超出了我的知识范围,但是如果你自己实现它并不难真的很需要。
spsc_queue
的基数 class 为 compile_time_sized_ringbuffer
或 runtime_sized_ringbuffer
,具体取决于队列大小在编译时是否已知。这两个 classes 维护实际使用的缓冲区,动态缓冲区和编译时缓冲区之间存在明显差异,但在这种情况下,将它们的 push
成员函数委托给公共基 class - 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
加载完成的(这是安全的,因为这个 class 的预期用途是 push
调用)并获取适当的下一个索引,检查以确保一切都在边界内(使用加载获取以与调用 pop
的线程适当同步),但我们感兴趣的主要语句是:
new (buffer + write_index) T(t); // copy-construct
它在缓冲区中执行放置新副本构造。传递一些参数以用于直接从可行的构造函数参数构造 T
本身并没有什么线程不安全的。我编写了以下代码片段并在派生的 classes 中进行了必要的更改,以适当地将工作委托给基础 class:
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
而不是复制构造。
常规 std::vector
有 emplace_back
可以避免不必要的复制。有什么理由 spsc_queue
不支持这个吗?是否由于某种原因无法使用无锁队列emplace
?
我既不是 boost 库的实现者也不是维护者,所以为什么不包含 emplace
成员函数背后的基本原理超出了我的知识范围,但是如果你自己实现它并不难真的很需要。
spsc_queue
的基数 class 为 compile_time_sized_ringbuffer
或 runtime_sized_ringbuffer
,具体取决于队列大小在编译时是否已知。这两个 classes 维护实际使用的缓冲区,动态缓冲区和编译时缓冲区之间存在明显差异,但在这种情况下,将它们的 push
成员函数委托给公共基 class - 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
加载完成的(这是安全的,因为这个 class 的预期用途是 push
调用)并获取适当的下一个索引,检查以确保一切都在边界内(使用加载获取以与调用 pop
的线程适当同步),但我们感兴趣的主要语句是:
new (buffer + write_index) T(t); // copy-construct
它在缓冲区中执行放置新副本构造。传递一些参数以用于直接从可行的构造函数参数构造 T
本身并没有什么线程不安全的。我编写了以下代码片段并在派生的 classes 中进行了必要的更改,以适当地将工作委托给基础 class:
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
而不是复制构造。