gpt4 book ai didi

c++ - Boost-定期任务计划程序

转载 作者:行者123 更新时间:2023-11-30 00:45:59 28 4
gpt4 key购买 nike

我正在尝试找出用于定期任务的简单调度程序。这个想法是要提供一种方法,以便在任何给定的时间间隔(即一秒的乘法)中安排std::function<void()>的定期执行。我试图使用boost::asio编写它,但是到目前为止,我最终遇到了奇怪的行为-只有两个计划任务中的一个正在重复执行,但它没有遵循间隔。

这是代码:

#include <functional>
#include <iostream>

#include <boost/asio.hpp>
#include <boost/bind.hpp>

class PeriodicTask
{
public:
PeriodicTask(boost::asio::io_service * ioService, int interval, std::function<void()> task)
: ioService(ioService),
interval(interval),
task(std::make_shared<std::function<void()>>(task)),
timer(std::make_shared<boost::asio::deadline_timer>(*ioService, boost::posix_time::seconds(interval)))
{}

void execute()
{
task->operator()();
timer->expires_at(timer->expires_at() + boost::posix_time::seconds(interval));
timer->async_wait(boost::bind(&PeriodicTask::execute,this));
}

private:
std::shared_ptr<boost::asio::io_service> ioService;
std::shared_ptr<boost::asio::deadline_timer> timer;
std::shared_ptr<std::function<void()>> task;
int interval;
};

class PeriodicScheduler
{
public:
void run()
{
for each (auto task in tasks)
{
task.execute();
}
io_service.run();
}
void addTask(std::function<void()> task, int interval)
{
tasks.push_back(PeriodicTask(&io_service, interval, task));
}
boost::asio::io_service io_service;

private:
std::vector<PeriodicTask> tasks;
};


void printCPUUsage()
{
std::cout << "CPU usage: " << std::endl;
}

void printMemoryUsage()
{
std::cout << "CPU usage: " << std::endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
PeriodicScheduler scheduler;

scheduler.addTask(printCPUUsage, 5);
scheduler.addTask(printMemoryUsage, 10);

scheduler.run();

return 0;
}

有谁知道是什么原因引起的?还是碰巧知道解决问题的更好方法?

非常感谢!

最佳答案

分析

罪魁祸首似乎是在非标准的for each (auto task in tasks)(Microsoft扩展)中,它基本上等效于for (auto task : tasks)。这意味着您在迭代时复制tasks vector 的元素,并在循环体内使用拷贝。

这在PeriodicTask::execute中变得有意义,特别是在

timer->async_wait(boost::bind(&PeriodicTask::execute, this));

其中 this指向上述拷贝,而不是 vector 中存储的对象。

我们可以添加一些简单的调试跟踪,以打印 vector 中对象的地址以及在其上调用 execute的对象的地址。还要在 vector中保留一些空间,这样就不会发生重新分配以简化操作的情况。

运行它时,我们将在控制台中看到以下内容:
>example.exe
02-11-2016 20-04-36 created this=22201304
02-11-2016 20-04-36 created this=22201332
02-11-2016 20-04-36 execute this=19922484
02-11-2016 20-04-36 CPU usage
02-11-2016 20-04-36 execute this=19922484
02-11-2016 20-04-36 Memory usage
02-11-2016 20-04-46 execute this=19922484
02-11-2016 20-04-46 Memory usage
02-11-2016 20-04-46 execute this=19922484
02-11-2016 20-04-46 Memory usage
02-11-2016 20-04-46 execute this=19922484
02-11-2016 20-04-46 Memory usage
02-11-2016 20-04-46 execute this=19922484
.... and so on and on and on....

让我们分析一下。让我们假设t是指开始时间。
  • 第1行:创建的CPU计时器@地址 22201304 ,设置为在t + 5秒后过期。
  • 第2行:创建的内存计时器@地址 22201332 ,设置为在t + 10秒时到期。
  • 第3,4行:制作了CPU计时器拷贝以解决 19922484 。跑处理程序。安排了CPU计时器,以在t + 5 + 5秒的时间在地址 19922484 上的对象上运行execute
  • 第5,6行:复制了Memory计时器,以解决 19922484 。跑处理程序。计划的内存计时器在t + 10 + 10秒内在地址 19922484 上的对象上运行execute

  • 在此阶段,我们有两个待处理的计时器,其中每十秒一秒,从启动开始每二十秒一秒。他们两个都计划在地址为 19922484 的对象上运行成员函数 execute,那时该对象不再存在(这在for循环中是临时的)。偶然地,内存中仍然包含来自占据该位置的最后一个对象的数据-内存任务的拷贝。

    时间流逝...
  • 第7,8行:CPU计时器启动,并在地址 19922484 的对象上运行execute。如上所述,这意味着该方法在Memory任务拷贝的上下文中运行。因此,我们看到“内存使用情况”已打印。

  • 此时,计时器将重新安排。由于我们的上下文,我们没有重新安排CPU计时器,而是重新安排了尚待处理的Memory计时器。这将导致挂起的异步等待操作被取消,进而导致到期处理程序被调用并传递错误代码 boost::asio::error::operation_aborted。但是,到期处理程序将忽略错误代码。因此
  • 第9,10行:取消触发Memory计时器到期处理程序,execute在地址为 19922484 的对象上运行。如上所述,这意味着该方法在Memory任务拷贝的上下文中运行。因此,我们看到“内存使用情况”已打印。在内存计时器上已经有一个待处理的异步等待,因此我们在重新计划时会导致另一个取消。
  • 第11,12行:取消...您的要旨。

  • 简单修复

    更改for循环以使用引用。
    for (auto& task : tasks) {
    // ....
    }

    控制台输出:
    >so02.exe
    02-11-2016 20-39-30 created this=19628176
    02-11-2016 20-39-30 created this=19628204
    02-11-2016 20-39-30 execute this=19628176
    02-11-2016 20-39-30 CPU usage
    02-11-2016 20-39-30 execute this=19628204
    02-11-2016 20-39-30 Memory usage
    02-11-2016 20-39-40 execute this=19628176
    02-11-2016 20-39-40 CPU usage
    02-11-2016 20-39-45 execute this=19628176
    02-11-2016 20-39-45 CPU usage
    02-11-2016 20-39-50 execute this=19628176
    02-11-2016 20-39-50 CPU usage
    02-11-2016 20-39-50 execute this=19628204
    02-11-2016 20-39-50 Memory usage
    02-11-2016 20-39-55 execute this=19628176
    02-11-2016 20-39-55 CPU usage

    更深入的分析

    我们已经解决了一个小问题,但是您提供的代码存在其他一些或多或少严重的问题。

    一个不好的做法是,您使用一个已经存在的 std::shared_ptr<boost::asio::io_service>实例( io_service的成员)的地址来初始化 PeriodicScheduler

    代码本质上是这样的:
    boost::asio::io_service io_service;
    std::shared_ptr<boost::asio::io_service> ptr1(&io_service);
    std::shared_ptr<boost::asio::io_service> ptr2(&io_service);

    这会创建该对象的3个彼此不认识的所有者。

    PeriodicTask不应是可复制的-这没有意义,并且可以避免上面解决的主要问题。我的猜测是其中的那些共享指针是为了解决复制它的问题(而 io_service本身是不可复制的)。

    最后,计时器的完成处理程序应具有 boost::system::error_code const&参数,至少应正确处理取消操作。

    完整的解决方案

    让我们从包含和一些便利的日志记录功能开始。
    #include <ctime>
    #include <iostream>
    #include <iomanip>
    #include <functional>

    #include <boost/asio.hpp>
    #include <boost/bind.hpp>
    #include <boost/noncopyable.hpp>

    void log_text(std::string const& text)
    {
    auto t = std::time(nullptr);
    auto tm = *std::localtime(&t);
    std::cout << std::put_time(&tm, "%d-%m-%Y %H-%M-%S") << " " << text << std::endl;
    }

    接下来,让 PeriodicTask显式不可复制,并保留对 io_service实例的引用。这意味着我们也可以避免其他共享指针。我们可以编写一个单独的方法来第一次对计时器进行 start,然后将其发布到 io_service上,以便由 run()执行。最后,让我们修改完成处理程序以处理错误状态,并在取消后正确运行。
    class PeriodicTask : boost::noncopyable
    {
    public:
    typedef std::function<void()> handler_fn;

    PeriodicTask(boost::asio::io_service& ioService
    , std::string const& name
    , int interval
    , handler_fn task)
    : ioService(ioService)
    , interval(interval)
    , task(task)
    , name(name)
    , timer(ioService)
    {
    log_text("Create PeriodicTask '" + name + "'");
    // Schedule start to be ran by the io_service
    ioService.post(boost::bind(&PeriodicTask::start, this));
    }

    void execute(boost::system::error_code const& e)
    {
    if (e != boost::asio::error::operation_aborted) {
    log_text("Execute PeriodicTask '" + name + "'");

    task();

    timer.expires_at(timer.expires_at() + boost::posix_time::seconds(interval));
    start_wait();
    }
    }

    void start()
    {
    log_text("Start PeriodicTask '" + name + "'");

    // Uncomment if you want to call the handler on startup (i.e. at time 0)
    // task();

    timer.expires_from_now(boost::posix_time::seconds(interval));
    start_wait();
    }

    private:
    void start_wait()
    {
    timer.async_wait(boost::bind(&PeriodicTask::execute
    , this
    , boost::asio::placeholders::error));
    }

    private:
    boost::asio::io_service& ioService;
    boost::asio::deadline_timer timer;
    handler_fn task;
    std::string name;
    int interval;
    };

    PeriodicScheduler保留 unique_ptr<PeriodicTask>的 vector 。由于 PeriodicTask现在可以自行处理入门,因此我们可以简化 run方法。最后,让我们也使其不可复制,因为复制它实际上没有多大意义。
    class PeriodicScheduler : boost::noncopyable
    {
    public:
    void run()
    {
    io_service.run();
    }

    void addTask(std::string const& name
    , PeriodicTask::handler_fn const& task
    , int interval)
    {
    tasks.push_back(std::make_unique<PeriodicTask>(std::ref(io_service)
    , name, interval, task));
    }

    private:
    boost::asio::io_service io_service;
    std::vector<std::unique_ptr<PeriodicTask>> tasks;
    };

    现在,让我们将它们放在一起并尝试一下。
    int main()
    {
    PeriodicScheduler scheduler;

    scheduler.addTask("CPU", boost::bind(log_text, "* CPU USAGE"), 5);
    scheduler.addTask("Memory", boost::bind(log_text, "* MEMORY USAGE"), 10);

    log_text("Start io_service");

    scheduler.run();

    return 0;
    }

    控制台输出:
    >example.exe
    02-11-2016 19-20-42 Create PeriodicTask 'CPU'
    02-11-2016 19-20-42 Create PeriodicTask 'Memory'
    02-11-2016 19-20-42 Start io_service
    02-11-2016 19-20-42 Start PeriodicTask 'CPU'
    02-11-2016 19-20-42 Start PeriodicTask 'Memory'
    02-11-2016 19-20-47 Execute PeriodicTask 'CPU'
    02-11-2016 19-20-47 * CPU USAGE
    02-11-2016 19-20-52 Execute PeriodicTask 'CPU'
    02-11-2016 19-20-52 * CPU USAGE
    02-11-2016 19-20-52 Execute PeriodicTask 'Memory'
    02-11-2016 19-20-52 * MEMORY USAGE
    02-11-2016 19-20-57 Execute PeriodicTask 'CPU'
    02-11-2016 19-20-57 * CPU USAGE
    02-11-2016 19-21-02 Execute PeriodicTask 'CPU'
    02-11-2016 19-21-02 * CPU USAGE
    02-11-2016 19-21-02 Execute PeriodicTask 'Memory'
    02-11-2016 19-21-02 * MEMORY USAGE

    关于c++ - Boost-定期任务计划程序,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40381838/

    28 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com