代码之家  ›  专栏  ›  技术社区  ›  pSoLT

Boost-周期性任务调度程序

  •  2
  • pSoLT  · 技术社区  · 8 年前

    我正在尝试为周期性任务制定一个简单的调度程序。其目的是提供一种安排定期执行的方法 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;
    }
    

    有人知道是什么导致了这个问题吗?或者碰巧知道解决问题的更好方法?

    非常感谢!

    1 回复  |  直到 8 年前
        1
  •  8
  •   Dan MaÅ¡ek    8 年前

    分析

    罪魁祸首似乎是非标准 for each (auto task in tasks) (微软扩展),基本上等同于 for (auto task : tasks) 这意味着您复制 tasks 向量,并在循环体中处理副本。

    这与 PeriodicTask::execute ,特别是

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

    哪里 this

    我们可以添加一些简单的调试跟踪,以打印向量中对象的地址以及 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 ,将于 .
    • 第3、4行:将CPU定时器复制到地址 19922484 .运行处理程序。计划运行的CPU计时器 处决 关于地址处的对象 19922484 t+5+5秒 .
    • 第5、6行:将内存计时器复制到地址 19922484 .运行处理程序。计划运行的内存计时器 处决 关于地址处的对象 19922484 在里面 t+10+10秒 .

    在这个阶段,我们有两个计时器等待,一个在10秒内,另一个在启动后20秒内。它们都计划运行成员函数 处决 19922484 ,此时已不存在(它是for循环中的临时项)。碰巧,内存中仍然包含来自占用该位置的最后一个对象的数据——内存任务的副本。

    时间流逝。。。

    • 第7行和第8行:CPU计时器触发并运行 处决 关于地址处的对象 19922484 如上所述,这意味着该方法在存储器任务的副本的上下文中运行。因此,我们看到“内存使用率”被打印出来。

    此时,重新安排计时器。由于我们的上下文,我们没有重新调度CPU计时器,而是重新调度仍然挂起的内存计时器。这将导致挂起的异步等待操作被取消,这将导致调用过期处理程序并传递错误代码 boost::asio::error::operation_aborted 但是,您的到期处理程序会忽略错误代码。因此

    • 第9、10行:取消触发内存定时器到期处理程序, 处决 在地址处的对象上运行 19922484 如上所述,这意味着该方法在存储器任务的副本的上下文中运行。因此,我们看到“内存使用率”被打印出来。内存计时器上已经有一个挂起的异步等待,因此我们在重新调度时会导致另一个取消。

    • 第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_服务 自身不可复制)。

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

    让我们从includes和一个方便的日志记录功能开始。

    #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;
    }
    

    下一步,让我们 周期任务 显式不可复制,并保留对 io_服务 例子这意味着我们也可以避免其他共享指针。我们可以编写一个单独的方法 start 定时器第一次,并将其张贴在 io_服务 ,因此它由 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;
    };
    

    让我们拥有 周期调度器 保持向量 unique_ptr<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