【问题标题】:Calling a function every 1 second (precisely)每 1 秒调用一次函数(精确)
【发布时间】:2018-10-12 16:12:42
【问题描述】:

我正在用 C++ 编写一个简单的游戏模拟程序,有一个名为 update() 的函数可以更新游戏的当前状态,它必须精确地每 1 秒调用一次。如果我使用这样的循环:

while(//some condition) {
     update();
     Sleep(1000);
}

那么该函数将不会每 1 秒调用一次,而是每 (1 + update () 的执行时间) 调用一次。 我阅读了各种解决方案,例如异步函数、多线程或使用 std::chrono 计算函数的执行时间并将其从 1000ms 参数中减去以休眠。其中一些对于我的简单案例来说太复杂了,如果我不太了解它们,其他一些似乎不安全。

谁能告诉我什么是适合我的要求的解决方案? 提前致谢。

【问题讨论】:

  • "使用 std::chrono 计算函数执行时间并从 1000ms 参数中减去它以休眠" 这是最简单的方法之一,我建议这样做。
  • "使用 std::chrono 计算函数执行时间并从 1000ms 中减去它":听起来您已经有了答案,您到底想从我们这里得到什么? 编辑哦,来吧!
  • 我用硬件定时器中断做过一次。硬件将在 1000 毫秒(正负大约 1 个时钟周期)处跳转到代码地址,执行代码(应该很短,并且需要不到 1000 毫秒,然后返回。硬件中断取决于硬件(不,呃),所以我不知道它是否适用于您的特定场景。这只是实现这一点的一种很好且准确的方法。
  • 你有一些event loop 吗?您在游戏中使用哪些图形库? SFMLQt 还是什么?
  • @the_storyteller 我知道你在说什么,但我在嵌入式系统中做过类似的事情,不知道如何在桌面应用程序中做到这一点。

标签: c++ multithreading asynchronous delay sleep


【解决方案1】:

您需要睡眠直到一个时间点,而不是睡眠一段时间。例如,如果您的第一次更新恰好在 2:00:00.000,那么您未来的更新应尽可能接近 2:00:01.000、2:00:02.000 等。

要实现这一点,您可以指定一个线程来更新,并在更新后进入睡眠状态直到下一次进行预定更新。 chrono::system_clock::time_pointthis_thread::sleep_until 是您执行此操作的工具。

例如:

#include <atomic>
#include <chrono>
#include <iostream>
#include <thread>

class UpdateManager
{
public:
    explicit UpdateManager() = default;

private:
    static std::atomic<int> now_;
    static std::atomic<bool> stop_;

    struct update_thread
        : private std::thread
    {
        ~update_thread();
        update_thread(update_thread&&) = default;

        using std::thread::thread;
    };

public:
    static update_thread start();
};

void update();

// source

std::atomic<int>  UpdateManager::now_{0};
std::atomic<bool> UpdateManager::stop_{false};

UpdateManager::update_thread::~update_thread()
{
    if (joinable())
    {
        stop_ = true;
        join();
    }
}

UpdateManager::update_thread
UpdateManager::start()
{
    return update_thread{[]
                         {
                             using namespace std;
                             using namespace std::chrono;
                             auto next = system_clock::now() + 1s;
                             while (!stop_)
                             {
                                 update();
                                 this_thread::sleep_until(next);
                                 next += 1s;
                             }
                         }};
}

#include "date/date.h"

void
update()
{
    using namespace date;
    using namespace std;
    using namespace std::chrono;
    cerr << system_clock::now() << '\n';
}

// demo

int
main()
{
    auto t = UpdateManager::start();
    using namespace std;
    this_thread::sleep_for(10s);
}

仅出于演示目的(逻辑不是必需的),我使用Howard Hinnant's, free, open-source date/time library 将当前时间 (UTC) 打印到微秒精度,以说明该技术的稳定性。该程序的示例输出是:

2018-05-02 15:14:25.634809
2018-05-02 15:14:26.637934
2018-05-02 15:14:27.636629
2018-05-02 15:14:28.637947
2018-05-02 15:14:29.638413
2018-05-02 15:14:30.639437
2018-05-02 15:14:31.637217
2018-05-02 15:14:32.637895
2018-05-02 15:14:33.637749
2018-05-02 15:14:34.639084

【讨论】:

  • 非常感谢!但这种答案是我的意思是对我的简单场景来说太复杂了。
  • 只需sleep_until 而不是sleep_for,你会没事的。
  • 谢谢,会查一下
  • @Yosry 相关部分是: using namespace std;使用命名空间 std::chrono;自动下一个 = system_clock::now() + 1s; while (!stop_) { update(); this_thread::sleep_until(下一个);下一个 += 1s; }
  • 关键是通过"now"获取起始时间点,设置下一个时间点以1s递增,"wait_until"下一个时间点。
【解决方案2】:

您可以避免使用 chrono 来代替这个更简单的选项。但是,如果您想要 μs-ns 精度,我强烈建议您使用 chrono。

#include <ctime>

while(//some condition) {

     std::clock_t start = std::clock();
     update();
     std::clock_t end   = std::clock();
     Sleep(1000 - (end - start)/CLOCKS_PER_SEC * 1000);
}

【讨论】:

  • Sleep 特定于 WinAPI。在 POSIX 上,它可能是其他东西,可能是 nanosleep(2)poll(2)
  • @BasileStarynkevitch 我在他的例子中使用了它。我认为 sleep 是他制作的自定义功能。
【解决方案3】:

按照其他一些人的建议,确保事情在正确的时间发生的最佳方法是在 while 循环中使用 this_thread::sleep_until()

我想确保我的循环从秒的底部开始,因此使用 chrono time_since_epoch 计算 next_full_second 时间点

interval 变量可以设置为秒或毫秒。使用open source date library by HowardHinnant 打印出漂亮的时间值。

#include <chrono>
#include <iostream>
#include <thread>

#include "date/date.h"

using namespace date;
using namespace std;
using namespace std::chrono;

int main()
{

    system_clock::time_point now = system_clock::now();
    auto s = duration_cast<seconds>(now.time_since_epoch());
    system_clock::time_point next_full_second = system_clock::time_point(++s);

    auto interval = seconds(1); // or milliseconds(500) or whatever
    auto wait_until = next_full_second;

    while (1)
    {
        this_thread::sleep_until(wait_until);
        cout << system_clock::now() << endl;

        // do something

        wait_until += interval;
    }

    return 0;
}

【讨论】:

    【解决方案4】:

    查看此伪代码。如果每个进程(可能需要超过 1 秒)在单独的线程中异步完成,您可以根据需要在下一个循环之前恰好休眠 1 秒。

    void MainGameLoop(bool& running)
    {
        while (running)
        {
            AnimateSpritesAsync();
            AnimateBulletsAsync();
            CheckCollisionAsync();
            CheckWinConditionsAsync();
    
            WaitPreciselyOneSecond();
        }
    }
    

    例如,您的 update 函数将如下所示:

    void update(); //your update 
    
    void UpdateAsync()  // the async update
    {
       std::thread([]() { update(); }).detach();
    }
    

    现在你可以:

    void MainGameLoop(bool& running)
        {
            while (running)
            {
                UpdateAsync();    
                Sleep(1000); // i won't recommend Sleep tho
            }
        }
    

    【讨论】:

    • 我不知道如何异步调用函数。
    猜你喜欢
    • 2011-11-06
    • 2011-03-09
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-05-17
    • 2018-02-16
    • 1970-01-01
    相关资源
    最近更新 更多