pSo*_*oLT 1 c++ multithreading boost boost-asio c++11
我正在试图为周期性任务找出一个简单的调度程序.该想法是提供一种方法来安排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;
}
Run Code Online (Sandbox Code Playgroud)
有谁知道什么可能导致问题?或碰巧知道更好的方法来解决问题?
非常感谢!
主要罪魁祸首似乎是非标准for each (auto task in tasks)(微软扩展),这基本上相当于for (auto task : tasks).这意味着您在tasks迭代它们时复制向量的元素,并使用循环体内的副本.
这在PeriodicTask::execute具体而言是相关的
timer->async_wait(boost::bind(&PeriodicTask::execute, this));
Run Code Online (Sandbox Code Playgroud)
其中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....
Run Code Online (Sandbox Code Playgroud)
我们来分析吧.我们假设t指的是开始时间.
execute在地址上对象19922484在T + 5 +5秒.execute在地址上对象19922484在T + 10±10秒.在这个阶段,我们有两个定时器待定,一个在10秒内,一个在20秒内启动.它们都被安排execute在地址为19922484的对象上运行成员函数,该对象在那时不再存在(它在for循环中是临时的).偶然地,内存仍然包含来自占用该位置的最后一个对象的数据 - 内存任务的副本.
时间流逝...
execute在地址19922484的对象上运行.如上所述,这意味着该方法在Memory任务的副本的上下文中运行.因此我们看到"内存使用"打印出来.此时,重新安排计时器.由于我们的上下文,我们重新安排仍然挂起的内存计时器,而不是重新安排CPU计时器.这会导致挂起的异步等待操作被取消,这将导致调用到期处理程序并传递错误代码boost::asio::error::operation_aborted.但是,您的到期处理程序会忽略错误代码.从而
第9,10行:取消触发内存计时器到期处理程序,execute在地址为19922484的对象上运行.如上所述,这意味着该方法在Memory任务的副本的上下文中运行.因此我们看到"内存使用"打印出来.内存计时器上已有待处理的异步等待,因此重新安排时会导致另一次取消.
第11,12行:取消......你得到了要点.
更改for循环以使用引用.
for (auto& task : tasks) {
// ....
}
Run Code Online (Sandbox Code Playgroud)
控制台输出:
>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
Run Code Online (Sandbox Code Playgroud)
我们已经解决了一个小问题,但是您提供的代码还存在其他一些或多或少的严重问题.
一个糟糕的是你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);
Run Code Online (Sandbox Code Playgroud)
它创建了该对象的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;
}
Run Code Online (Sandbox Code Playgroud)
接下来,让我们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;
};
Run Code Online (Sandbox Code Playgroud)
让我们PeriodicScheduler保持一个向量unique_ptr<PeriodicTask>.由于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;
};
Run Code Online (Sandbox Code Playgroud)
现在,让我们把它们放在一起然后尝试一下.
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;
}
Run Code Online (Sandbox Code Playgroud)
控制台输出:
>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
Run Code Online (Sandbox Code Playgroud)