我有一组必须使用多线程同时处理的数据,据说数据的数量大于线程的数量。我决定将数据放入某种队列中,这样每个自由线程都可以弹出它的部分并处理它直到队列为空。当我想要从中取出一个元素时,我可以使用一个简单的STL队列并用互斥锁将其锁定,但我想尝试一种无锁方法。与此同时,我的项目太小,无法依赖某些提供无锁结构的第三方库,实际上我只需要原子队列化。所以我决定基于一个带有指向“head”的指针的向量来实现我自己的队列,并以原子方式递增这个指针:
template <typename T>
class AtomicDequeueable
{
public:
// Assumption: data vector never changes
AtomicDequeueable(const std::vector<T>& data) :
m_data(data),
m_pointer(ATOMIC_VAR_INIT(0))
{}
const T * const atomicDequeue()
{
if (std::atomic_load(&m_pointer) < m_data.size())
{
return &m_data
[
std::atomic_fetch_add(&m_pointer, std::size_t(1))
];
}
return nullptr;
}
private:
AtomicDequeueable(const AtomicDequeueable<T>&) {}
std::atomic_size_t m_pointer;
const std::vector<T>& m_data;
};
Threads'函数如下所示:
void f(AtomicDequeueable<Data>& queue)
{
while (auto dataPtr = queue.atomicDequeue())
{
const Data& data = *dataPtr;
// processing data...
std::this_thread::sleep_for(std::chrono::milliseconds(1));
}
}
我使用无锁结构和原语的经验非常糟糕,所以我想知道:我的方法会正常工作吗?当然我已经在Ideone上测试了它,但我不知道它对真实数据的表现如何。
答案 0 :(得分:1)
目前,您的atomicDequeue
函数有一个数据竞争:2个线程可能在执行第二个之前执行第一个atomic
指令。但是,这可以修复,因为您实际上只需要1个原子操作,按照以下更改:
const T * const atomicDequeue()
{
auto myIndex = std::atomic_fetch_add(&m_pointer, std::size_t(1));
if(myIndex >= m_data.size())
return nullptr;
return &m_data[myIndex];
}
这项工作在线程操作期间没有修改输入向量。
答案 1 :(得分:-1)
你的代码非常错误。让我在这里的建议中非常坦诚:
“ Actum Ne Agas:不要做已经完成的事情。”你已经很多预先存在的C ++类可供您使用,可以可靠地进行排队,以及进程间通信(IPC)。使用其中一个。
不要担心“无锁”。这就是锁定 for ,它们可靠,快速,便宜。
你的“使用队列”这个概念是合理的,但是你会做出不必要的工作......并且制造错误......当你可以简单地“抓住现成的东西”时。您知道标准部件可以正常工作,因为其他人已将其测试为死亡。