原子出队的最简单方法?
Posted
技术标签:
【中文标题】原子出队的最简单方法?【英文标题】:The simpliest way to dequeue atomically? 【发布时间】:2016-07-07 15:14:15 【问题描述】:我有一组数据必须使用多线程同时处理,据说数据的数量大于线程的数量。我决定将数据放入某种队列中,这样每个空闲线程都可以弹出它的部分并处理它,直到队列为空。当我想从中取出一个元素时,我可以使用一个简单的 STL 队列并通过互斥锁将其锁定,但我想尝试一种无锁方法。同时我的项目太小,不能依赖一些提供无锁结构的第三方库,实际上我只需要原子出队。所以我决定基于一个带有指向“头”的指针的向量来实现我自己的队列,并原子地增加这个指针:
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 上对其进行了测试,但我不知道它在真实数据中的表现如何。
【问题讨论】:
无锁且简单?好家伙。您的代码中存在数据竞争。想象一下,您的队列n
被填充到n - 1
,现在两个线程同时在std::atomic_load(&m_pointer) < m_data.size()
中成功:一旦它们执行原子增量,您将拥有UB,因为一个线程将访问m_data[ n - 1 ]
,另一个将访问@ 987654327@.
你的atomicDequeue
函数看起来不是原子的。想想当m_pointer
是m_data.size()-1
时,如果两个线程通过if 语句会发生什么。
【参考方案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];
如果在线程操作期间没有修改输入向量,则此方法有效。
【讨论】:
【参考方案2】:您的代码有很多错误。让我在这里非常坦率地提出我的建议:
“Actum Ne Agas: 不要做已经做过的事情。” 你有大量预先存在的 C++ 类可供您使用,它可以可靠地进行排队和一般的进程间通信(IPC)。使用其中之一。
不用担心“无锁”。这就是锁的用途,而且它们可靠、快速且便宜。
您“使用队列”的概念是合理的,但是当您可以简单地“从货架上拿东西”时,您会做不必要的工作......并制造错误......您知道标准部分会正常工作,因为其他人已经将其测试到死。
【讨论】:
以上是关于原子出队的最简单方法?的主要内容,如果未能解决你的问题,请参考以下文章
使用出队的 dequeueReusableCell - 出队后单元格的 textField
动态/可出队的 UITableviewcells 内的 UITextfield