2015-11-14 45 views
0

我有一些代码,是这样的:暂停对空队列线程,而无需使用自旋锁

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使用率,这似乎是自旋锁的结果时,有什么为工作人员做。我试图使用一个互斥锁,但是它会要求主线程在队列中没有任何东西时锁定它(当然不会发生这种情况)。有什么替代方法来防止这种情况?

+1

既然你不想使用同步对象,使用睡眠 '而(a_queue.empty())的std :: this_thread :: sleep_for(STD ::时辰::毫秒(1) );'你确定条件变量不适合你吗? – AlexStepanov

+0

你可以在处理同步的队列周围放置一个包装器吗? –

+0

@AlexStepanov这是一个游戏项目,其帧之间的“截止时间”约为16ms,因此等待1ms不是一种选择。 – user112513312

回答

0

下面的代码是我在其他地方学过的东西。它是一个阻塞队列工具。线程可以安全地将元素放入队列中,并且如果某个线程在队列为空时尝试从队列中取出元素,它将被阻塞,直到某个其他线程将元素放入队列。希望它可以帮助你

#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; 
    } 

}; 
0

鉴于规定的要求,其中线程调度器/上下文切换的成本过于昂贵,通常是最好的办法是简单地烧周期为你现在要做的,以满足最严格的时延要求。

一个原子CAS旋转/忙碌循环基本上是一个轮询方法,并且通常与轮询相关联,它有一个趋势CPU。然而,它并没有支付您想要避免的调度程序的成本,并且在16毫秒的最后期限内完成您需要做的所有事情并提供框架。达到这种最后期限通常是你最好的选择。

随着游戏中你有一个生动的世界和不断动画的内容,通常没有漫长的闲置期间没有任何事情发生。因此,一般认为游戏经常使用CPU是完全可以接受的。

考虑到您的要求,可能一个更高效的问题不是如何减少CPU利用率,以至于确保CPU利用率达到良好的目的。通常,并发队列可以提供无锁,无堵塞查询来检查,看看是否队列为空,你似乎已经给了这行:

while (a_queue.empty()); 

你也许可以在一些潜行在队列为空时在这里​​计算的东西。通过这种方式,你不会耗尽周期而仅仅是繁忙 - 在队列中等待非空。

同样,通常对于您的问题的理想答案会涉及条件变量(这取决于上下文切换和线程被唤醒)。它通常是让线程进入睡眠状态(降低CPU利用率)并在需要时唤醒的最快方式,但考虑到延迟要求严格,最好忘记CPU利用率并更多关注确保它有一个好的目标。

+0

'鉴于线程调度程序/上下文切换成本太高的规定要求,' - 它在哪里说的?它提到16ms,与典型的线程间通信内核信令延迟5-10us相比,它是永恒的。 –

+0

从前面的讨论 - 有人提到,即使条件变量是不可取的。我假设这样一个严格的等待时间要求,无论是条件变量/监视器的开销在传递帧之前都会增加(尽管也许最好只使用一个时间/看到它)。无论如何,我只是想提出,即使条件变量太慢,那么典型的替代方法就是在繁忙的循环中烧掉循环。看起来不可能同时对延迟和更低的CPU使用率产生沉迷。 –

+0

虽然我99%与你达成一致。我宁愿只是使用条件变量,而不必担心它,直到有足够的理由这样做。 1%可能只是我认为还有另外一种情况,其中spinloop可能是合理的,那就是当你的速度概念不是基于平均值,而是基于一致性/稳定性时,这可能在我们不喜欢的游戏中常见我们不想要任何偏僻的可能性 - “一致的速度”而不是简单的“快速”。虽然5-10我们真的陷入了一个不健康的痴迷领域 - 我想我唯一想提供的是我们... –

0

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

作为编码的,如果多个线程队列中的等待数据,它们可以所有作用于从a_queue.fronf()相同的值,则调用a_queue.pop()一些无关的值的数量次推队列上。

Thread safety for STL queue