在不使用自旋锁的情况下暂停空队列上的线程

Pausing thread on empty queue without using spinlock

本文关键字:暂停 情况下 队列 线程      更新时间:2023-10-16

我有一些代码是这样工作的:

std::queue<int> a_queue;
bool exit = false;
void MainThreadFunc(int somedata)
{
    a_queue.push(somedata);
}
void WorkerThreadFunc()
{
    while (true)
    {
        if (exit)
            return;
        while (a_queue.empty());
        DoSomethingWithData(a_queue.front());
        a_queue.pop();
    }
}

问题是,我的CPU使用率非常高,这似乎是由于工作线程无事可做时的自旋锁。我试图使用互斥锁,但我需要主线程在队列中无事时锁定它(当然,这是不可能的)。有什么替代方案可以防止这种情况发生?

下面的代码是我之前在其他地方学到的。它是一个阻塞队列实现。线程可以安全地将元素放入队列,如果某个线程试图在元素为空时从队列中取出元素,则它将被阻止,直到其他线程将元素放入该队列。希望它能帮助你

#include <queue>
#include <cassert>
#include <mutex>
#include <condition_variable>
#include <thread>
template<typename T>
class BlockingQueue
{
private:
    std::mutex _mutex;
    std::condition_variable _condvar;
    std::queue<T> _queue;
public:
    BlockingQueue(): _mutex(),_condvar(),_queue()
    {
    }
    BlockingQueue(const BlockingQueue& rhs) = delete;
    BlockingQueue& operator = (const BlockingQueue& rhs) = delete;
    void Put(const T& task)
    {
        {
            std::lock_guard<std::mutex> lock(_mutex);
            _queue.push(task);
        }
        _condvar.notify_all();
    }
    T Take()
    {
        std::unique_lock<std::mutex> lock(_mutex);
        _condvar.wait(lock,[this]{return !_queue.empty(); });
        assert(!_queue.empty());
        T front(std::move(_queue.front()));
        _queue.pop();
        return front;
    }
};

考虑到线程调度程序/上下文切换的成本过于昂贵的既定要求,通常最好的选择是像现在这样简单地燃烧周期,以满足最严格的延迟要求。

原子CAS自旋/繁忙循环基本上是一种轮询方法,通常与轮询相关,它有占用CPU的趋势。然而,它并没有支付调度器的成本,而在16毫秒的最后期限内,你需要完成所有需要做的事情并交付一个帧,这是你想要避免的。这通常是你最好的选择,以满足这种最后期限。

在游戏中,你有一个生动的世界和不断的动画内容,通常不会有长时间的无所事事。因此,对于一个不断使用CPU的游戏来说,这通常被认为是可以接受的。

考虑到您的需求,可能一个更有效的问题不是如何降低CPU利用率,而是确保CPU利用率达到良好的目的。通常,并发队列可以提供一个无锁、无阻塞的查询来检查队列是否为空,您似乎已经给出了以下行:

while (a_queue.empty());

当队列为空时,您可能可以偷偷地在这里计算一些东西。这样一来,您就不会仅仅忙于等待队列变为非空而占用周期。

同样,你的问题的理想答案通常包括一个条件变量(这取决于上下文开关和被唤醒的线程)。这通常是让线程进入睡眠状态(降低CPU利用率)并在所需时间唤醒线程的最快方法,但考虑到延迟要求很高,最好忘记CPU利用率,更多地关注确保它达到良好的目的。

std::queue不是线程安全的。正如@Hurkyl发布的那样,如果你想使用std::queue,你需要一个锁。

按照编码,如果多个线程在队列中等待数据,它们可以所有都对a_queue.fronf()中的同一个值执行操作,然后多次调用a_queue.pop(),与推送到队列上的值的数量无关。

请参阅STL队列的线程安全