未延迟的无限 while 循环是不好的做法吗?

Is an un-delayed infinite while loop bad practice?

简而言之: 与因延迟而变慢的类似循环相比,未延迟的 while 循环是否消耗大量处理能力?

不那么短:

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

我将举这个例子:我有一个使用 SDL window 和控制台的小应用程序。在一个 while 循环中,我想听这个 SDL window、 的事件,我也想根据命令行输入通过全局来打破这个循环多变的。可能的解决方案(伪代码):

// 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 次,足够了。

那么。这两者有本质区别吗?重要吗?它在微控制器单元上是否更重要(处理能力非常有限(但除了程序之外没有其他需要 运行...))?

最好的确定方法是自己测量。

未延迟的循环将导致应用 运行 所在的特定核心使用率达到 100%。使用延迟声明,它将在 0 - 1% 左右。 (以getEvent函数的即时响应为准)

好吧,这取决于几个因素 - 如果您不需要 运行 除了并行循环之外的任何其他内容,显然不会产生任何性能差异。 但可能会出现的一个问题是功耗——取决于这个循环的长度,在第二个变体中,您可能会节省微控制器所消耗的大约 90% 的功率。 总的来说,将其称为不良做法对我来说似乎并不正确 - 它适用于很多场景。

据我了解 while 循环,进程仍然保存在 ram 中。所以它不会让处理器在给定的延迟期间使用它的资源。它在第二个代码中的唯一区别是给定时间内 while 循环的执行次数。如果程序长时间 运行,这会有所帮助。否则第一种情况没问题。

好吧,实际上这不是关于 C++ 的问题,而是答案取决于 CPU 架构/主机 OS / delay() 实现。

  1. 如果它是一个多任务环境,那么 delay() 可以(并且可能会)帮助 OS 调度程序更有效地完成它的工作。然而,真正的区别可能太小而无法注意到(旧的协作式多任务处理除外,其中 delay() 是必须的)。

  2. 如果是单任务环境(可能是某些微控制器),如果底层实现能够执行一些专用的低功耗指令而不是普通循环,则 delay() 仍然有用。但是,当然,不能保证一定会,除非您的手册明确说明。

考虑到性能问题,很明显您可以接收和处理具有显着延迟(甚至完全错过)的事件,但如果您认为情况并非如此,那么就没有其他反对延迟的缺点( ).

您将使您的代码更难阅读,并且您正在以旧的方式执行异步:您明确地等待某事发生,而不是依赖为您完成工作的机制。 另外,您延迟了 50 毫秒。它总是最优的吗?它取决于哪些程序是 运行? 在 C++11 中,您可以使用 condition_variable。这允许您等待事件发生,而无需编写等待循环。

此处的文档: 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;
}

根据我的经验,您必须做一些会放弃处理器的事情。 sleep 工作正常,在大多数 windows 系统上,甚至 sleep(1) 也足以在循环中完全卸载处理器。

但是,如果您使用 std::condition_variable 之类的东西,您可以获得世界上最好的东西。可以使用条件变量来构造(类似于 'events' 和 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();
}        

如果时间空闲,您的消息处理循环将花费最多时间,等待条件变量。当一条消息被发送,条件变量被发送线程释放,你的接收线程会立即响应。

这允许您的接收线程以等待时间设置的最小速率和发送线程确定的最大速率循环。

您要问的是如何正确实施必须使用的 Event Loop. Use OS calls. You ask the OS for event or message. If no message is present, the OS simply sends the process to sleep. In a micro-controller environment you probably don't have an OS. There the concept of interrupts,这几乎是较低级别的 "message"(或事件)。

对于微控制器,您没有睡眠或中断等概念,因此您只能以循环结束。

在您的示例中,正确实施的 getEvent() 应该阻止并且在实际发生某些事情之前不执行任何操作,例如按键。