C ++线程安全队列关闭

时间:2016-09-15 13:36:13

标签: c++ multithreading concurrency queue

我在C ++中使用此类进行生产者 - 消费者设置:

#pragma once

#include <queue>
#include <mutex>
#include <condition_variable>
#include <memory>
#include <atomic>

template <typename T> class SafeQueue
{
public:
    SafeQueue() :
    _shutdown(false)
    {

    }

    void Enqueue(T item)
    {
        std::unique_lock<std::mutex> lock(_queue_mutex);
        bool was_empty = _queue.empty();
        _queue.push(std::move(item));
        lock.unlock();

        if (was_empty)
            _condition_variable.notify_one();
    }

    bool Dequeue(T& item)
    {
        std::unique_lock<std::mutex> lock(_queue_mutex);

        while (!_shutdown && _queue.empty())
            _condition_variable.wait(lock);

        if(!_shutdown)
        {
            item = std::move(_queue.front());
            _queue.pop();

            return true;
        }

        return false;
    }

    bool IsEmpty()
    {
        std::lock_guard<std::mutex> lock(_queue_mutex);
        return _queue.empty();
    }

    void Shutdown()
    {
        _shutdown = true;
        _condition_variable.notify_all();
    }

private:
    std::mutex _queue_mutex;
    std::condition_variable _condition_variable;
    std::queue<T> _queue;
    std::atomic<bool> _shutdown;
};

我这样使用它:

class Producer
{
public:
    Producer() :
        _running(true),
        _t(std::bind(&Producer::ProduceThread, this))
    { }

    ~Producer()
    {
        _running = false;
        _incoming_packets.Shutdown();
        _t.join();
    }

    SafeQueue<Packet> _incoming_packets;

private:
    void ProduceThread()
    {
        while(_running)
        {
            Packet p = GetNewPacket();
            _incoming_packets.Enqueue(p);
        }
    }

    std::atomic<bool> _running;
    std::thread _t;
}

class Consumer
{
    Consumer(Producer* producer) :
        _producer(producer),
        _t(std::bind(&Consumer::WorkerThread, this))
    { }

    ~Consumer()
    {
        _t.join();
    }

private:
    void WorkerThread()
    {
        Packet p;

        while(producer->_incoming_packets.Dequeue(p))
            ProcessPacket(p);
    }

    std::thread _t;
    Producer* _producer;
}

这个大部分时间。但是有一段时间我删除了制作人(导致它的解构函数调用SafeQueue::Shutdown_t.join()会永远阻止。

我的猜测就是问题就在这里(在SafeQueue::Dequeue中):

while (!_shutdown && _queue.empty())
        _condition_variable.wait(lock);
线程#1中的

SafeQueue::Shutdown在线程#2完成检查 _shutdown 但在执行_condition_variable.wait(lock)之前被调用,因此它&#34;未命中&#34; notify_all()。这会发生吗?

如果这是问题所在,解决问题的最佳方法是什么?

2 个答案:

答案 0 :(得分:1)

由于SafeQueue对象由生产者拥有,因此删除生产者会导致被通知的消费者与生产者完成时被删除的SafeQueue之间的竞争条件。

我建议共享资​​源既不是生产者也不是消费者,而是作为对每个资源的构造函数的引用传递。

更改Producer和Consumer构造函数;

Producer( SafeQueue<Packet> & queue ) :
    _running(false), _incoming_packets(queue) {}


Consumer( SafeQueue<Packet> & queue ) :
    _running(false), _incoming_packets(queue) {}

以这种方式使用您的实例;

SafeQueue<Packet> queue;
Producer producer(queue);  
Consumer consumer(queue);

...do stuff...

queue.shutdown();

这也解决了您在Consumer类中与Producer类紧密耦合的不良设计问题。

此外,在析构函数中杀死和连接线程可能是一个坏主意,就像你为~Producer所做的那样。最好为每个线程类添加Shutdown()方法,并显式调用它们;

producer.shutdown();
consumer.shutdown();
queue.shutdown();

关闭顺序并不重要,除非您担心在停止使用者时丢失仍在队列中的未处理数据包。

答案 1 :(得分:0)

在您的 return Ok(myResult); // gives emphasis on status, my personal favorite return new OkObjectResult(myResult); // for me a little bit verbose and the same // effect as Ok; but states we return an Object return new ObjectResult(myResult); // gives emphasis of the content that is returned 中,您可能以错误的方式使用SafeQueue::Dequeue ...更改此信息:

std::condition_variable

bool Dequeue(T& item)
{
    std::unique_lock<std::mutex> lock(_queue_mutex);

    while (!_shutdown && _queue.empty())
        _condition_variable.wait(lock);
    if(!_shutdown)
    {
        item = std::move(_queue.front());
        _queue.pop();
        return true;
    }
    return false;
}

其次,bool Dequeue(T& item) { std::unique_lock<std::mutex> lock(_queue_mutex); _condition_variable.wait(lock, []{ return _shutdown || !_queue.empty() }); if(!_shutdown) { item = std::move(_queue.front()); _queue.pop(); return true; } return false; } 中数据成员的初始化顺序与构造函数的关系不正确

Consumer

应重新订购:

class Consumer
{
    Consumer(Producer* producer) :
        _producer(producer),
        _t(std::bind(&Consumer::WorkerThread, this))
    { }
    ......
    // _t will be constructed first, regardless of your constructor initializer list
    // Meaning, the thread can even start running using an unintialized _producer
    std::thread _t;      
    Producer* _producer;
}

问题的另一部分由CAB's answer

涵盖