C++ 固定大小的线程安全队列

C++ 固定大小的线程安全队列,c++,multithreading,thread-safety,queue,buffer,C++,Multithreading,Thread Safety,Queue,Buffer,我想做的是将整数推送到我的线程安全队列实现中,该队列实现包含多个线程,并与另一系列线程同时弹出插入的数字。所有这些操作都必须是线程安全的,但我想要的另一个选择是队列的大小必须是固定的,就像缓冲区一样。如果缓冲区已满,则所有推送线程必须等待pop线程释放一些插槽 这是我对队列/缓冲区的实现,它似乎可以工作,但经过几次迭代后,它停止并保持阻塞状态,没有任何错误 #include <queue> #include <thread> #include <mutex>

我想做的是将整数推送到我的线程安全队列实现中,该队列实现包含多个线程,并与另一系列线程同时弹出插入的数字。所有这些操作都必须是线程安全的,但我想要的另一个选择是队列的大小必须是固定的,就像缓冲区一样。如果缓冲区已满,则所有推送线程必须等待pop线程释放一些插槽

这是我对队列/缓冲区的实现,它似乎可以工作,但经过几次迭代后,它停止并保持阻塞状态,没有任何错误

#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <iostream>

template <typename T>

class Queue
{
private:
    std::queue<T> queue_;
    std::mutex mutex_;
    std::condition_variable cond_;

public:

    T pop()
    {
        std::unique_lock<std::mutex> mlock(mutex_);

        cond_.wait(mlock, [this]{return !queue_.empty();});

        auto val = queue_.front();
        queue_.pop();
        return val;
    }

    void pop(T& item)
    {
        std::unique_lock<std::mutex> mlock(mutex_);

        cond_.wait(mlock, [this]{return !queue_.empty();});

        item = queue_.front();
        queue_.pop();
    }

    void push(const T& item, int buffer)
    {
        std::unique_lock<std::mutex> mlock(mutex_);

        while (queue_.size() >= buffer)
        {
            cond_.wait(mlock);
        }

        queue_.push(item);
        mlock.unlock();
        cond_.notify_one();
    }

    Queue()=default;
    Queue(const Queue&) = delete;            // disable copying
    Queue& operator=(const Queue&) = delete; // disable assignment

};
#包括
#包括
#包括
#包括
#包括
模板
类队列
{
私人:
std::队列;
std::mutex mutex;
std::条件变量cond;
公众:
T pop()
{
std::unique_lock mlock(互斥锁);
cond.wait(mlock,[this]{return!queue_u.empty();});
auto val=队列前();
queue_u2;.pop();
返回val;
}
无效pop(T和项目)
{
std::unique_lock mlock(互斥锁);
cond.wait(mlock,[this]{return!queue_u.empty();});
item=队列前面();
queue_u2;.pop();
}
无效推送(常量T和项目,整数缓冲区)
{
std::unique_lock mlock(互斥锁);
while(队列大小()>=缓冲区)
{
条件等待(mlock);
}
队列推送(项目);
mlock.unlock();
条件通知一个();
}
Queue()=默认值;
队列(const Queue&)=删除;//禁用复制
队列和运算符=(常量队列&)=delete;//禁用分配
};
缓冲区的大小在带有变量buffer的push函数中定义。这是一个用法示例:

 void prepare(Queue<int>& loaded, int buffer, int num_frames)
 {
     for (int i = 0; i < num_frames; i++)
     {
         cout<< "push "<<i<<endl;
         loaded.push(i, buffer);
     }
 }

 void load (vector<Frame>& movie, Queue<int>& loaded, int num_frames,
                            int num_points, int buffer, int height, int width)
    {
        for (int i = 0; i < num_frames; i++)
        {
            int num = loaded.pop();
            cout<< "pop "<<num<<endl;
    }
}

int main()
{
    srand(time(NULL));

    int num_threadsXstage = 4;

    int width = 500;
    int height = 500;

    int num_points = width * height;

    int num_frames = 100;

    int frames_thread = num_frames/num_threadsXstage;

    int preset = 3;

    int buffer = 10;

    //Vectors of threads
    vector<thread> loader;

    //Final vector
    vector<Frame> movie;
    movie.resize(num_frames);

    //Working queues
    Queue<int> loaded;

    //Prepare loading queue task
    thread preparator(prepare, ref(loaded), buffer, num_frames);

    for (int i = 0; i < num_threadsXstage; i++)
    {
        //stage 1
        loader.push_back(thread(&load, ref(movie), ref(loaded), frames_thread,
                                num_points, buffer, height, width));

    }


    // JOIN
    preparator.join();

    join_all(loader);

    return 0;
}
void prepare(队列和加载、int缓冲区、int num_帧)
{
对于(int i=0;icout您的
pop
函数可以允许等待
push
的线程向前推进,但它们不调用任何
notify
函数。您必须在任何时候调用适当的
notify
函数,以便在条件变量上阻塞的线程向前推进

尽管解释原因相当复杂,但您应该在保持锁的同时调用
notify_all
或调用
notify_one
。否则,理论上可能会“唤醒错误的线程”,因为您对两个谓词使用相同的条件变量(队列不是空的,队列也不是满的)

为避免难以理解的故障模式,请始终执行以下三项操作之一:

  • 不要使用同一个条件变量来处理多个谓词。例如,使用一个条件变量表示“notempty”,另一个表示“notfull”
  • 始终使用
    notify_all
    ,从不使用
    notify_one
    ;或
  • 保持互斥时始终调用notify函数

  • 只要您至少遵守这三条规则中的一条,就可以避免出现一种模糊的故障模式,即您只唤醒一个在释放互斥锁后选择睡眠的线程,而让唯一可以处理该情况的线程仍然被阻塞。

    pop()
    中缺少一个
    notify()
    来发出
    push()的信号
    现在就可以推了。另外,正如前面指出的,您需要在通知时保持锁定。好的,谢谢,抱歉,但我对多线程和条件变量非常陌生,我不完全理解如何修改代码,以及这三件事中哪一件更适合我的问题。@CIVI89最重要的事情是调用弹出时使用notify函数。这将解决导致您提出此问题的问题。至于使用三种方法中的哪一种,它们中的任何一种都可以在您的情况下正常工作。您可以使用两个条件变量,在保持锁的同时始终发信号,或者每次都调用notify_。这三种方法中的任何一种都可以修复罕见的竞争条件您可能永远不会真正命中。@TFM在这种情况下,丢失信号没有坏处,因为线程不会等待。它将检查谓词,确定它不需要等待,也不需要等待。只有当线程正在等待时,信号才是宝贵的,并且该线程必须在更改谓词之前看到谓词。我们需要锁来更改它,所以它一定已经释放了它。因此它已经在等待,不能错过信号。(但正如我在回答中所说,这很难理解。所以只需遵循三条规则之一,您就不必担心。)@TFM想一想。在发信号之前解锁所做的一切都是稍后发信号。任何在你持有锁时等待的线程都将在稍后等待,除非它被唤醒。等待发信号的唯一方法是如果你发信号“错误的线程”,一个在释放锁后才开始等待的规则。这三条规则中的任何一条都可以避免这个问题。@DavidSchwartz是的,当然,我很困惑。需要保护的是谓词,而不是条件变量。