【问题标题】:Critique my concurrent queue [closed]批评我的并发队列[关闭]
【发布时间】:2010-07-07 05:06:44
【问题描述】:

这是我编写的并发队列,我计划在我正在编写的线程池中使用它。我想知道是否可以进行任何性能改进。 atomic_counter 贴在下面,如果你好奇的话!

#ifndef NS_CONCURRENT_QUEUE_HPP_INCLUDED
#define NS_CONCURRENT_QUEUE_HPP_INCLUDED

#include <ns/atomic_counter.hpp>
#include <boost/noncopyable.hpp>
#include <boost/smart_ptr/detail/spinlock.hpp>
#include <cassert>
#include <cstddef>

namespace ns {
    template<typename T,
             typename mutex_type = boost::detail::spinlock,
             typename scoped_lock_type = typename mutex_type::scoped_lock>
    class concurrent_queue : boost::noncopyable {
        struct node {
            node * link;
            T const value;
            explicit node(T const & source) : link(0), value(source) { }
        };
        node * m_front;
        node * m_back;
        atomic_counter m_counter;
        mutex_type m_mutex;
    public:
        // types
        typedef T value_type;

        // construction
        concurrent_queue() : m_front(0), m_mutex() { }
        ~concurrent_queue() { clear(); }

        // capacity
        std::size_t size() const { return m_counter; }
        bool empty() const { return (m_counter == 0); }

        // modifiers
        void push(T const & source);
        bool try_pop(T & destination);
        void clear();
    };

    template<typename T, typename mutex_type, typename scoped_lock_type>
    void concurrent_queue<T, mutex_type, scoped_lock_type>::push(T const & source) {
        node * hold = new node(source);
        scoped_lock_type lock(m_mutex);
        if (empty())
            m_front = hold;
        else
            m_back->link = hold;
        m_back = hold;
        ++m_counter;
    }

    template<typename T, typename mutex_type, typename scoped_lock_type>
    bool concurrent_queue<T, mutex_type, scoped_lock_type>::try_pop(T & destination) {
        node const * hold;
        {
            scoped_lock_type lock(m_mutex);
            if (empty())
                return false;
            hold = m_front;
            if (m_front == m_back)
                m_front = m_back = 0;
            else
                m_front = m_front->link;
            --m_counter;
        }
        destination = hold->value;
        delete hold;
        return true;
    }

    template<typename T, typename mutex_type, typename scoped_lock_type>
    void concurrent_queue<T, mutex_type, scoped_lock_type>::clear() {
        node * hold;
        {
            scoped_lock_type lock(m_mutex);
            hold = m_front;
            m_front = 0;
            m_back = 0;
            m_counter = 0;
        }
        if (hold == 0)
            return;
        node * it;
        while (hold != 0) {
            it = hold;
            hold = hold->link;
            delete it;
        }
    }
}

#endif

atomic_counter.hpp

#ifndef NS_ATOMIC_COUNTER_HPP_INCLUDED
#define NS_ATOMIC_COUNTER_HPP_INCLUDED

#include <boost/interprocess/detail/atomic.hpp>
#include <boost/noncopyable.hpp>

namespace ns {
    class atomic_counter : boost::noncopyable {
        volatile boost::uint32_t m_count;
    public:
        explicit atomic_counter(boost::uint32_t value = 0) : m_count(value) { }

        operator boost::uint32_t() const {
            return boost::interprocess::detail::atomic_read32(const_cast<volatile boost::uint32_t *>(&m_count));
        }

        void operator=(boost::uint32_t value) {
            boost::interprocess::detail::atomic_write32(&m_count, value);
        }

        void operator++() {
            boost::interprocess::detail::atomic_inc32(&m_count);
        }

        void operator--() {
            boost::interprocess::detail::atomic_dec32(&m_count);
        }
    };
}

#endif

【问题讨论】:

  • 您有具体的问题要问,或者有什么需要解决的问题吗? SO 不是代码审查网站。
  • 我进行了编辑,表明我对性能改进建议感兴趣。
  • 您看过 Herb Sutter 的 DDJ 文章 (drdobbs.com/cpp/211601363) 吗?他讨论了一些性能改进(对推送和弹出使用不同的锁,为缓存​​行大小填充成员变量)。更一般地说,你为什么要实现自己的而不是使用,例如待定concurrent_queue?
  • try_pop 中存在潜在的内存泄漏。如果复制构造函数抛出destination = hold-&gt;value,那么hold 将永远不会被删除。解决方案是做std::queue 所做的事情:访问和删除队列前面的项目的单独方法。或者(正如 Gunslinger47 所说)使用 std::queue 而不是重新发明它。
  • @Mike 感谢您发现潜在的泄漏。那将是赋值运算符,对吗?我正在考虑使用一种虚拟对象,其析构函数在hold 上调用delete,因此如果赋值运算符确实抛出,则该节点将在堆栈展开时被删除。关于使用单独的方法来访问和删除:这在并发线程中是不切实际的。

标签: c++ concurrency queue


【解决方案1】:

我认为在这种情况下你会遇到链表的性能问题,因为每个新节点都调用了new。这不仅仅是因为调用动态内存分配器很慢。这是因为频繁调用它会引入大量并发开销,因为在多线程环境中,free store 必须保持一致。

当它太小而无法容纳队列时,我会使用你调整大小的向量。我永远不会将其缩小。

我会安排前值和后值,所以向量是一个环形缓冲区。这将要求您在调整大小时移动元素。但这应该是一个相当罕见的事件,并且可以通过在构建时给出建议的向量大小来在一定程度上缓解。

或者,您可以保留链表结构,但永远不要破坏节点。只需继续将其添加到空闲节点队列中即可。不幸的是,空闲节点队列需要锁定才能正确管理,我不确定你是否真的比一直调用 delete 和 new 更好。

您还将使用矢量获得更好的参考位置。但我不确定这将如何与必须在 CPU 之间来回穿梭的缓存行交互。

其他人建议使用::std::deque,我认为这不是一个坏主意,但我怀疑环形缓冲区向量是一个更好的主意。

【讨论】:

  • 我认为环形缓冲区不需要设计为支持可变容量。就像windows消息队列一样,它的容量是固定大小的。我很少看到有人抱怨这个。
【解决方案2】:

Herb Sutter 提出了一个无锁队列的实现,它肯定会胜过你的 :)

主要思想是使用缓冲环,在队列运行期间完全放弃动态分配内存。这意味着队列可能已满(因此您可能必须等待放入元素),这在您的情况下可能是不可接受的。

正如 Omnifarious 所指出的,最好不要使用链表(用于缓存位置),除非您为池分配。我会尝试使用std::deque 作为后端,它对内存更友好,并且只要您只弹出和推送(在前面和后面)就不会发生任何重新分配,这通常是队列的情况。

【讨论】:

  • 我不知道 Herb Sutter 的想法。 :-) 我将不得不查看细节,但我敢打赌,您可以实现与我建议的相同类型的扩展(但从不收缩)缓冲区。
  • 可能,请参阅 Cliff 博士的 Google Talk 单击实现 Java 的无锁哈希表。核心思想当然是一个动态数组:video.google.com/videoplay?docid=2139967204534450862#,很好的是重新分配的成本分散在多次写入中,所以当需要重新分配时你不会冻结一个线程。但是你还是要问为了记忆,这总是需要一些时间。
  • 只是一个简短的说明,Herb 没有使用环作为通用并发队列,而是一个简单的缓冲区环实现仅适用于单个读取器、单个写入的情况。如果您想要多个生产者/消费者,那么循环缓冲区的问题会变得更加复杂。
  • @shrin:确切地说,它是一种简单的机制,但在这方面存在严重限制。另一方面,Fast Flow 使用 SPSC 队列作为构建 MPSC、SPMC 和 MPMC 队列的基础,因此一旦您获得了快速的 SPSC 实现,您就可以“相对”轻松地获得其他的。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-06-27
  • 2014-02-28
  • 1970-01-01
  • 2012-05-19
  • 1970-01-01
相关资源
最近更新 更多