Timer.4 使用成员函数作为completion handler
在这个例子中,我们将看到如何使用一个类成员函数作为completion handler。这个程序应该与Timer.3 执行相同的操作。
#include <iostream>
#include <asio.hpp>
#include <boost/bind/bind.hpp>
不是像之前例子中定义一个自由函数print作为completion handler,我们现在定义一个printer的类。
class printer{
public:
这个类的构造函数将使用一个io_context对象的引用作为参数,并且使用它初始化timer_成员。这个用来关闭程序的计数器现在也是类的一个成员。
printer(asio::io_context& io)
: timer_(io,asio::chrono::seconds(1)),
count_(0){
boost::bind函数对于类成员函数也可以像自由函数那样发挥作用。因为所有非静态类成员函数都有一个隐式的this参数,我们需要向这个函数绑定this。正如timer.3,boost::bind将我们的completion handler转化为一个函数对象,仿佛它有签名void(const asio::error_code&)
。
你讲注意到asio::placeholders::error占位符在这里并没有出现,因为print成员函数并不接受一个错误对象作为参数。
timer_.async_wait(boost::bind(&printer::print, this));
}
在这个类的析构函数,我们将打印计数器最后的值。
~printer(){
std::cout << "Final count is " << count_ << std::endl;
}
这个print成员函数与timer.3的print函数非常相似,除了它现在是在类的数据成员上操作,而不是传入timer和counter作为参数。
void print(){
if(count_ < 5){
std::cout << count_ << std::endl;
++count_;
timer_.expires_at(timer_.expiry() + asio::chrono::seconds(1));
timer.async_wait(boost::bind(&printer::print, this));
}
}
private:
asio::steady_timer timer_;
int count_;
};
主函数比之前简单,如下
int main(){
asio::io_context io;
printer p(io);
io.run();
return 0;
}
使用标准库bind,如下:
#include <asio.hpp>
#include <iostream>
#include <functional>
class printer {
public:
printer(asio::io_context& io)
: timer_(io, asio::chrono::seconds(1)),
count_(0){
timer_.async_wait(std::bind(&printer::print, this));
}
~printer(){
std::cout << "Final count is " << count_ << std::endl;
}
void print(){
if(count_ < 5){
std::cout << count_ << std::endl;
++count_;
timer_.expires_at(timer_.expiry() + asio::chrono::seconds(1));
timer_.async_wait(std::bind(&printer::print, this));
}
}
private:
asio::steady_timer timer_;
int count_;
};
int main(){
asio::io_context io;
printer p(io);
io.run();
return 0;
}
Timer.5 在多线程程序中同步completion handler
这个示例展示strand类模板的使用来在多线程程序中同步completion handler。先前4个示例避免了handler的同步问题,通过只在一个线程中调用io_context::run()函数。正如你已经知道的,asio库保证了completion handler将只会在当前调用io_context::run()的线程中调用。因此,只在一个线程中调用io_context::run()确保了completion handler不能并发地运行。
单线程方案通常是一个好的起点,但它的负面是一些程序上的限制,尤其是服务器,包括:
- 很差的灵敏性,当handlers需要花费大量时间完成
- 不能扩展到多处理器系统
如果你发现自己陷入这些限制中,一个替代的方案是拥有一个线程池来调用io_context::run()。然而,因为这允许handlers并发执行,当handlers可能访问共享或是线程不安全的资源时,我们需要同步的方法。
#include <iostream>
#include <asio.hpp>
#include <boost/bind/bind.hpp>
我们从定义printer类开始。这个类将会扩展为并行执行2个timer。
class printer
{
public:
除了初始化一对asio::steady_timer成员,构造函数也需要初始化strand_成员,这是类型为asio::strandasio::io_context::executor_type的对象。
strand 类模板是一个执行器适配器,可以保证通过它分派的handler,在下一个handler启动之前,一个正在执行的handler将被允许完成。无论调用 io_context::run() 的线程数量有多少,都能保证这一点。当然,处理程序仍有可能与其他处理程序同时执行,而这些处理程序不是通过strand调度的,就是通过不同的strand对象调度的。
printer(asio::io_context& io)
: strand_(asio::make_strand(io)),
timer1_(io, asio::chrono::seconds(1)),
timer2_(io, asio::chrono::seconds(1)),
count_(0)
{
当发起异步操作时,每个completion handler都与一个asio::strandasio::io_context::executor_type对象相关联。这个asio::bind_executor()函数返回一个新的handler,它可以通过strand对象自动分发它所包含的handler。通过将handler绑定到相同的strand,我们确信它们不能并发执行。
timer1_.async_wait(asio::bind_executor(strand_,
boost::bind(&printer::print1, this)));
timer2_.async_wait(asio::bind_executor(strand_,
boost::bind(&printer::print2, this)));
}
~printer()
{
std::cout << "Final count is " << count_ << std::endl;
}
在一个多线程程序中,如果需要访问共享资源,异步操作的handler应该被同步。在这个示例中,由print1和2使用的共享资源时std::cout和count_成员。
void print1()
{
if (count_ < 10)
{
std::cout << "Timer 1: " << count_ << std::endl;
++count_;
timer1_.expires_at(timer1_.expiry() + asio::chrono::seconds(1));
timer1_.async_wait(asio::bind_executor(strand_,
boost::bind(&printer::print1, this)));
}
}
void print2()
{
if (count_ < 10)
{
std::cout << "Timer 2: " << count_ << std::endl;
++count_;
timer2_.expires_at(timer2_.expiry() + asio::chrono::seconds(1));
timer2_.async_wait(asio::bind_executor(strand_,
boost::bind(&printer::print2, this)));
}
}
private:
asio::strand<asio::io_context::executor_type> strand_;
asio::steady_timer timer1_;
asio::steady_timer timer2_;
int count_;
};
主函数现在在两个线程中触发io_context::run()函数,一个在主函数中,另一个在额外的线程中。
int main()
{
asio::io_context io;
printer p(io);
asio::thread t(boost::bind(&asio::io_context::run, &io));
io.run();
t.join();
return 0;
}
使用标准库的bind,如下:
#include <asio.hpp>
#include <iostream>
#include <functional>
class printer {
public:
printer(asio::io_context& io):
strand_(asio::make_strand(io)),
timer1_(asio::steady_timer(io, asio::chrono::seconds(1))),
timer2_(asio::steady_timer(io, asio::chrono::seconds(1))),
count_(0){
timer1_.async_wait(asio::bind_executor(strand_, std::bind(&printer::print1, this)));
timer2_.async_wait(asio::bind_executor(strand_, std::bind(&printer::print2, this)));
}
~printer(){
std::cout << "Final count is " << count_ << std::endl;
}
void print1(){
if(count_ < 10){
std::cout << "Timer 1: " << count_ << std::endl;
++count_;
timer1_.expires_at(timer1_.expiry() + asio::chrono::seconds(1));
timer1_.async_wait(asio::bind_executor(strand_, std::bind(&printer::print1, this)));
}
}
void print2(){
if(count_ < 10){
std::cout << "Timer 2: " << count_ << std::endl;
++count_;
timer2_.expires_at(timer2_.expiry() + asio::chrono::seconds(1));
timer2_.async_wait(asio::bind_executor(strand_, std::bind(&printer::print2, this)));
}
}
private:
asio::strand<asio::io_context::executor_type> strand_;
asio::steady_timer timer1_;
asio::steady_timer timer2_;
int count_;
};
int main(){
asio::io_context io;
printer p(io);
asio::thread t([&io](){ io.run(); });
io.run();
t.join();
return 0;
}