无延迟的无限循环是否是不良实践?

9

简而言之:相比于通过延迟减速的类似循环,未经延迟的while循环是否会消耗大量的处理能力?

详细解释:

我经常遇到这个问题。我正在编写程序的核心部分(无论是微控制器单元还是计算机应用程序),它由一个半无限的while循环组成,以保持活动状态并寻找事件。

我将采用以下示例:我有一个使用SDL窗口和控制台的小型应用程序。在while循环中,我想要监听此SDL窗口的事件,但是我也希望通过全局变量根据命令行输入来打破这个循环。可能的解决方案(伪代码):

// Global
bool running = true;

// ...

while (running)
{
    if (getEvent() == quit)
    {
        running = false;
    }
}

shutdown();

核心 while 循环将从被监听事件或一些外部因素退出。 然而,这个循环连续运行,甚至可能每秒运行1000次。这有些过度了,我不需要那么快的响应时间。因此,我经常添加一个延迟语句:

while (running)
{
    if (getEvent() == quit)
    {
        running = false;
    }
    delay(50); // Wait 50 milliseconds
}

这将刷新率限制为每秒20次,这已经足够了。

那么。两者之间是否有真正的区别?是否显著?在微控制器单元上(处理能力非常有限(但除程序外没有其他需求运行...)),它是否更为重要?


计算机非常快,通常可以轻松处理这个问题。话虽如此,您可以更好地实现getEvent()函数。 - Jake Freeman
这完全取决于 while 循环内部的线程控制流程。在多线程应用程序中使用 delay()sleep() 等东西很少是一个好主意。 - user0042
@MichaëlRoy,这就是我不确定的部分。这段代码真的会使用100%的CPU功率吗:while (true) {}。毕竟,它根本不需要任何操作。 - Roberto
如果问题涉及到SDL,则可以选择垂直同步或等待事件。如果涉及到控制台,则通常也可以等待stdin(例如,使用select)。 - keltar
这段代码真的会使用100%的CPU吗:while (true) {}?合理的假设是“这是平均值”。这意味着应该有更多“更环保”的替代方案。 - Matt
显示剩余7条评论
7个回答

5
实际上这不是有关C++的问题,答案取决于CPU架构/主机操作系统/delay()实现。
如果是多任务环境,则delay()可能会(并且很可能会)帮助操作系统调度程序更有效地完成工作。然而,实际差异可能微不足道(除了旧的合作式多任务处理中必须使用delay())。
如果是单任务环境(可能是一些微控制器),则如果底层实现能够执行一些专用的低功耗指令而不是普通的循环,则delay()仍然可能有用。但是,当然,除非您的手册明确说明,否则不能保证会这样做。
考虑到性能问题,显然您可能会收到和处理事件的显着延迟(甚至完全错过它),但是如果您认为这不是一个问题,则没有其他缺点来反对delay()。

这里的答案非常清晰,谢谢。--- 在我的实际项目中,延迟函数实际上是 SDL_WaitEventTimeout(),因此不会错过事件的风险。那么我将使用后者。 - Roberto

3
你会使代码更难读,以旧式异步方式处理:你明确等待某些事情发生,而不是依赖能够为你完成工作的机制。 此外,你延迟了50ms。这总是最优的吗?它是否取决于正在运行哪些程序? 在C++11中,你可以使用条件变量。这允许你等待事件发生,而无需编写等待循环。
文档在这里: http://en.cppreference.com/w/cpp/thread/condition_variable 我已经修改了示例,使其更易于理解。只等待单个事件。
以下是适用于你上下文的示例。
// Example program
#include <iostream>
#include <string>
#include <iostream>
#include <string>
#include <thread>
#include <mutex>
#include <chrono>
#include <condition_variable>

std::mutex m;
std::condition_variable cv;
std::string data;
bool ready = false;
bool processed = false;
using namespace std::chrono_literals;

void worker_thread()
{
    // Wait until main() sends data
    std::unique_lock<std::mutex> lk(m);

    std::cout << "Worker thread starts processing data\n";

    std::this_thread::sleep_for(10s);//simulates the work

    data += " after processing";

    // Send data back to main()
    processed = true;
    std::cout << "Worker thread signals data processing completed"<<std::endl;
    std::cout<<"Corresponds to you getEvent()==quit"<<std::endl;

    // Manual unlocking is done before notifying, to avoid waking up
    // the waiting thread only to block again (see notify_one for details)
    lk.unlock();
    cv.notify_one();
}

int main()
{
    data = "Example data";

    std::thread worker(worker_thread);    
    // wait for the worker
    {
        std::unique_lock<std::mutex> lk(m);

        //this means I wait for the processing to be finished and I will be woken when it is done. 
        //No explicit waiting
        cv.wait(lk, []{return processed;});
    }

    std::cout<<"data processed"<<std::endl;
}

1
你所询问的是如何正确实现Event Loop。使用操作系统调用。你向操作系统请求事件或消息。如果没有消息,则操作系统会将进程置于休眠状态。在微控制器环境中,可能没有操作系统。这时需要使用interrupts的概念,它基本上是较低级别的“消息”(或事件)。
对于微控制器,你没有类似于休眠或中断的概念,因此只能使用循环。
在你的示例中,一个正确实现的getEvent()应该阻塞并无动作,直到有实际发生的事件,例如按键。

1
在我的经验中,你必须做一些能够放弃处理器的事情。sleep可以正常工作,在大多数Windows系统上,即使是sleep(1)也足以在循环中完全卸载处理器。
但是,如果使用类似于std::condition_variable这样的东西,则可以兼顾所有方面。可以使用条件变量构造(类似于Windows API中的“事件”和WaitForSingleObject)。
一个线程可以阻塞在由另一个线程释放的条件变量上。这样,一个线程可以执行condition_varaible.wait(some_time),它将等待超时时间(而不会加载处理器),或者当另一个线程释放它时,它将立即继续执行。
我在一个线程向另一个线程发送消息的情况下使用此方法。我希望接收线程尽快响应,而不是等待sleep(20)完成后再响应。例如,接收线程有一个condition_variable.wait(20)。发送线程发送消息,并进行相应的condition_variable.release()。接收线程将立即释放并处理该消息。
这种解决方案对消息的响应非常快,并且不会过度加载处理器。
如果您不关心可移植性,并且恰好使用Windows,则事件和WaitForSingleObject会执行相同的操作。
你的循环代码看起来像这样:

while(!done)
{
    cond_var.wait(std::chrono::milliseconds(20));

    // process messages...
    msg = dequeue_message();


    if(msg == done_message)
        done = true;
    else
        process_message(msg);

}

在另一个线程中...
send_message(string msg)
{
    enqueue_message(msg);
    cond_var.release();
}        

你的消息处理循环将花费大部分时间处于空闲状态,等待条件变量。当发送线程发送消息并释放条件变量时,接收线程将立即响应。
这使得接收线程以等待时间为最小速率循环,并以发送线程确定的最大速率运行。

在 Gabriel 回答的时候,我正在打我的答案。他的回答是我伪代码回答所要表达的真实代码示例。 - ttemple

0

最好的方法是自己进行测量。

没有延迟的循环将导致该应用程序正在运行的特定核心的100%使用率。使用延迟语句,它将在0-1%左右。 (取决于getEvent函数的立即响应)


0

这取决于几个因素 - 如果除了该循环之外您不需要并行运行其他任何内容,则显然不会产生性能差异。 但可能会出现的问题是功耗 - 根据此循环的持续时间,您可能会节省第二种变体中微控制器消耗的电力的90%左右。 总的来说,称其为不良实践似乎不太合适 - 在许多情况下都可以使用。


0
据我所知,关于while循环,该过程仍然保留在内存中。因此,在给定的延迟期间,它不会让处理器使用其资源。第二段代码唯一的区别是在给定时间内while循环的执行次数。这对于程序长时间运行时有所帮助。否则,第一种情况没有问题。

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