并发队列在出队时卡住

时间:2018-08-03 13:46:00

标签: c++ concurrency

我编写了以下代码来实现并发队列。

template <typename T>
class ConcurrentQueue
{
    // Internal storage for a queue element
    struct Element
    {
        T m_elem;
        std::mutex m_mtx;
        std::condition_variable m_cv;
        bool m_hasElement = false;
    };

public:
    // The number of enqueued elements cannot go beyond p_capacity.
    ConcurrentQueue(size_t p_capacity) :
        m_elements(p_capacity),
        m_approxCount(0),
        m_actualCount(0),
        m_front(0),
        m_back(0)
    {}

    // Enqueues an element to the queue. Returns true on success and false
    // if the enqueue failed due to the capacity being reached.
    bool Enqueue(T p_element)
    {
        if (++m_approxCount > m_elements.size())
        {
            --m_approxCount;
            return false;
        }

        ++m_actualCount;

        size_t slot = m_back.fetch_add(1) % m_elements.size();
        auto& element = m_elements[slot];

        std::lock_guard<std::mutex> lk(element.m_mtx);
        element.m_elem = std::move(p_element);
        element.m_hasElement = true;
        element.m_cv.notify_one();

        return true;
    }

    // Dequeues an element from the queue. Returns true on success and false
    // if the dequeue failed due to the queue being empty.
    bool Dequeue(T& p_element)
    {
        size_t count = m_actualCount.load();
        if (count == 0)
        {
            return false;
        }

        while (!m_actualCount.compare_exchange_strong(count, count - 1))
        {
            if (count == 0)
            {
                return false;
            }
        }

        size_t slot = m_front.fetch_add(1) % m_elements.size();
        auto& element = m_elements[slot];

        std::unique_lock<std::mutex> lk(element.m_mtx);
        element.m_cv.wait(lk, [&element] { return element.m_hasElement; });
        p_element = std::move(element.m_elem);
        element.m_hasElement = false;
        --m_approxCount;

        return true;
    }

private:
    // Fixed size vector that stores the elements
    std::vector<Element> m_elements;

    // Approx count of number of elements in the queue.
    std::atomic<size_t> m_approxCount;

    // Actual count of the number of elements in the queue
    std::atomic<size_t> m_actualCount;

    // Index to the front of the queue
    std::atomic<size_t> m_front;

    // Index to the back of the queue
    std::atomic<size_t> m_back;
};

和以下测试以验证其功能

int main()
{
    int numElements = 1000;
    int numThreads = 10;
    ConcurrentQueue<int> q(numElements * numThreads / 2);

    std::vector<std::thread> enqueueThreads;
    for (int i = 0; i < numThreads; ++i)
    {
        enqueueThreads.emplace_back([&q, i, numElements]
        {
            for (int j = 0; j < numElements; ++j)
            {
                while (!q.Enqueue(i * numElements + j));
            }
        });
    }

    std::atomic<int> toDequeue = numElements * numThreads;
    std::vector<std::thread> dequeueThreads;
    for (int i = 0; i < numThreads; ++i)
    {
        dequeueThreads.emplace_back([&q, &toDequeue]
        {
            while (toDequeue > 0)
            {
                int element;
                if (q.Dequeue(element))
                {
                    --toDequeue;
                }
            }
        });
    }

    for (auto& t : enqueueThreads)
    {
        t.join();
    }

    for (auto& t : dequeueThreads)
    {
        t.join();
    }
}

在调试版本(VS2017)中,测试运行良好,但在零售版本中,main函数未返回(Dequeue线程未完成),表明存在错误。 ConcurrentQueue实现。 EnqueueDequeue方法中的错误是什么?

1 个答案:

答案 0 :(得分:0)

如果出队器尚未释放插槽,则Enqueue方法需要等待插槽可用。 以下代码解决了该问题。

template <typename T>
bool ConcurrentQueue<T>::Enqueue(T p_element)
{
    if (++m_approxCount > m_elements.size())
    {
        --m_approxCount;
        return false;
    }

    size_t slot = m_back.fetch_add(1) % m_elements.size();
    auto& element = m_elements[slot];

    {
        std::unique_lock<std::mutex> lk(element.m_mtx);
        element.m_cv.wait(lk, [&element] { return !element.m_hasElement; });
        element.m_elem = std::move(p_element);
        element.m_hasElement = true;
        element.m_cv.notify_all();
    }

    ++m_actualCount;
    return true;
}

template <typename T>
bool ConcurrentQueue<T>::Dequeue(T& p_element)
{
    size_t count = UINT64_MAX;
    while (!m_actualCount.compare_exchange_strong(count, count - 1))
    {
        if (count == 0)
        {
            return false;
        }
    }

    size_t slot = m_front.fetch_add(1) % m_elements.size();
    auto& element = m_elements[slot];

    {
        std::unique_lock<std::mutex> lk(element.m_mtx);
        element.m_cv.wait(lk, [&element] { return element.m_hasElement; });
        p_element = std::move(element.m_elem);
        element.m_hasElement = false;
        element.m_cv.notify_all();
    }

    --m_approxCount;
    return true;
}