等待条件的空循环(忙等待)

4

我刚刚花了20分钟时间研究只是为了等待条件变为真的空循环。

我有一个名为"waitForLoaded"的函数,它是由CreateThread创建的线程。

这个函数:

void waitForLoaded(){
    while(!isLoaded){
        Sleep(500); // < my question
    }
    Sleep(500); //sleep another 500ms to ensure everything is loaded.
    //continue on here
}

我正在使用Sleep(500)来减轻CPU负担,因为我认为使用0或1会耗尽处理器。
我在许多人的代码中看到了"Sleep(0)"的使用,但我从未理解为什么不完全不休眠,而是使用"while(condition){}.."。
我找不到任何关于哪个更加CPU友好的确定答案,所以我在这里问大家,使用0ms、1ms或500ms的忙等待有什么区别,哪个更加CPU友好。
在我看来,最好进行至少半个睡眠,对用户几乎不可察觉。

“你个人会怎么做”是一个基于个人观点的问题,这并不是本网站的主要目的。 - Drew Dormann
1
@DrewDormann,你说得完全正确,我应该问为什么一个比另一个更好。我会改变我的问题。 - Thomas White
sleep(0) 至少会导致线程重新调度。它并不等同于“根本没有睡眠”。你睡多久由你决定。你代码中的最后一个睡眠是无意义的。 - user207421
如果你的应用程序是单线程的,调用waitForLoaded将会永远冻结你的应用程序! - Maher
实际上,@EJP,这是必需的,信不信由你,因为设置“isLoaded”的函数并不完全准确,因为您需要考虑系统延迟。最终版本需要在末尾进行500毫秒的休眠,因为一些用户在没有它的情况下遇到了问题。感谢您的建议。 - Thomas White
1
@Maher 当然它不是单线程的,如果应用程序冻结,我就不会以同样的方式提出问题了...谢谢您的回答。 - Thomas White
5个回答

10
在Windows系统中,Sleep(0)并不会花费任何时间来睡眠,但它允许操作系统将CPU控制权交给其他等待的线程。这有点像说:“如果有人在排队等待,请让他们先走,否则我想立即执行。”

非常好的答案,解释得非常清楚易懂,对我非常有帮助。感谢回答我的问题。我从未确切知道“线程重新调度”是什么意思。 - Thomas White

2
一个简单的同步原语,比如事件或类似的东西,会消耗更少的CPU资源,而且你的线程希望能够尽快开始工作,而不是等待最坏情况下的500毫秒。

我之前听说过“同步原语”,但从未深入了解其含义。我会进行研究,谢谢。 - Thomas White

1
如果我理解你的问题,你正在询问这些等待方法中哪一个更好:
  • sleep(500)
  • sleep(1)
  • sleep(0)
  • // (do nothing)
如果你有时间可以承受 sleep(500),那么答案是 "sleep(500)"。

我也喜欢你的回答,我认为尽可能地让CPU休息是有意义的,因此要睡眠。我需要更好地了解“线程重新调度”,以便完全理解其他人谈论的使用Sleep(0)的好处。谢谢。 - Thomas White

0

首先,您需要研究您的问题。

  • 您是否需要忙等待?
  • 您能否使用调度程序?
  • 您能否检测到数据可用或操作完成的确切时刻?

我建议您尝试不同的方法,如事件文件描述符或条件变量。

条件变量方法:

boost::mutex::scoped_lock lock(m_mutex);
while(queue.empty() && !m_quit) {
    m_condition.wait(lock);
}    

事件文件描述符方法

m_loopFD = eventfd(0,EFD_CLOEXEC|EFD_NONBLOCK);
if(m_loopFD < 0) {
    close(m_epollFD);
    throw ...
}
struct epoll_event event;
memset(&event, 0, sizeof(struct epoll_event));
event.data.fd = m_loopFD;
event.events = EPOLLIN;
if(epoll_ctl(m_epollFD, EPOLL_CTL_ADD, m_loopFD, &event) != 0) {
    throw ...
}

稍后你可能会有类似这样的东西
int res = epoll_wait(m_epollFD, events, MAX_EVENTS, timeout);

并将其唤醒:

uint64_t value = 0x01;
write(m_loopFD, &value, sizeof(value));

0

忙等待基本上与必须计算某些内容以仅浪费时间的代码相关。如果需要等待相当长的时间,则Sleep会使用操作系统调度程序,这意味着对于小于调度程序时间量的时间段(Windows操作系统约为15ms),它不稳定。

例如,在自旋锁的情况下,这是不可接受的。

我能够得到的最简单的代码是:

#include <cstdlib>

inline void noop_by_rand(int num)
{
    while (num--) rand();
}

优点:

  • 它是一个内置函数,计算时间固定,小于操作系统调度器时间量。
  • 可以轻松地在更长的时间上进行扩展。
  • 编译器优化不能避免调用或减少代码,因为外部函数。
  • 依赖于CPU性能而不是时间,这意味着它随着CPU性能的提高而扩展。

缺点:

  • 无法避免操作系统调度程序。如果繁忙等待时间太长,则操作系统调度程序将处理线程以进行调度,这意味着它可能会失去比请求的时间更多的时间。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接