考虑一个无锁并发数据结构的情况,其中pop()
操作需要返回一个项目或false
如果该投币器是空的(而不是阻塞或投掷)。数据结构是在用户类型T
上模板化的,它可能很大(但也可能很轻,我希望在任何一种情况下都能提高效率)。 T
必须至少可移动,但我不希望它必须是可复制的。
我认为函数签名将是bool DS<T>::pop(T &item)
,因此项目被提取为out-parameter而不是return值(而不是用于表示成功或失败)。但是,我如何实际将其传递出去?假设有一个底层缓冲区。我会做item = std::move(_buff[_tail])
- 进入参考输出参数是否有意义?缺点是用户必须传入一个默认构造的T,这与有效的RAII有点相反,因为如果函数失败,结果是一个实际上没有初始化其资源的对象。
另一种选择是返回std::pair<bool, T>
而不是使用out-parameter,但是对于{{{}},还需要一个默认可构造的T
,在失败的情况下不保留任何资源。 1}}。
第三个选项是将项目作为return std::make_pair(false, T)
返回,但是在std::unique_ptr<T>
是指针或其他轻量级类型的情况下,这会产生无用的开销。虽然我可以在数据结构中存储指针,但实际项目存储在外部,这不仅会导致额外解除引用和缓存未命中的损失,而且还会消除直接存储在缓冲区中的项目添加的自然填充,并有助于最大限度地减少生产者和消费者线程来自命中相同的缓存行。
答案 0 :(得分:1)
#include <boost/optional.hpp>
#include <string>
template<class T>
struct atomic_queue
{
using value_type = T;
auto pop() -> boost::optional<T>
{
boost::optional<T> result;
/*
* insert atomic ops here, optionally filling result
*/
return result;
};
auto push(T&& arg) -> bool
{
/*
* insert atomic ops here, optionally stealing arg
*/
return true;
};
static auto make_empty_result() {
return boost::optional<T>();
}
};
struct difficult {
difficult(std::string);
difficult() = delete;
difficult(difficult const&) = delete;
difficult& operator=(difficult const&) = delete;
difficult(difficult &&) = default;
difficult& operator=(difficult &&) = default;
};
extern void spin();
int main()
{
atomic_queue<difficult> q;
auto d = difficult("arg");
while(not q.push(std::move(d)))
spin();
auto popped = q.make_empty_result();
while(not (popped = q.pop()))
spin();
auto& val = popped.get();
}