原子出队的最简单方法?

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(&amp;m_pointer) &lt; m_data.size() 中成功:一旦它们执行原子增量,您将拥有UB,因为一个线程将访问m_data[ n - 1 ],另一个将访问@ 987654327@. 你的atomicDequeue 函数看起来不是原子的。想想当m_pointerm_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)。使用其中之一。

不用担心“无锁”。这就是锁的用途,而且它们可靠、快速且便宜。

您“使用队列”的概念是合理的,但是当您可以简单地“从货架上拿东西”时,您会做不必要的工作......并制造错误......您知道标准部分会正常工作,因为其他人已经将其测试到死。

【讨论】:

以上是关于原子出队的最简单方法?的主要内容,如果未能解决你的问题,请参考以下文章

UVA540-队列

使用出队的 dequeueReusableCell - 出队后单元格的 textField

出队的 UICollectionViewCell 未刷新

动态/可出队的 UITableviewcells 内的 UITextfield

出队的 UITableViewCell 在滚动之前布局不正确(使用自动布局)

如何防止出队的集合视图单元格相互重叠?