本教程使用了简单的异步计时器演示了asio的基本使用。
同步使用定时器
如何实现阻塞等待定时器。首先引入头文件
#include <iostream>
#include <boost/asio.hpp>
"asio.hpp"可以简单地帮我们将所需的头文件引入。
使用asio的所有程序都需要至少一个I/O execution context,像io_context
或者thread_pool
对象。通过I/O execution context我们来访问I/O功能。
在主函数中声明一个io_context
的对象
int main() {
boost::asio::io_context io;
}
接下来再声明一个boost::asio::steady_timer
类型的对象。asio中提供I/O功能的核心类(例如本例中的定时器)总是将io_context
作为第一个参数。在本例的定时器中,将过期时间设置为第二个参数。
boost::asio::steady_timer t(io,boost::asio::chrono::seconds(5));
在这个简单的例子中,我们对定时器执行阻塞等待。也就是说,对 steady_timer::wait()
的调用将不会返回,直到定时器到期,即创建后 5 秒。
定时器始终处于以下两种状态之一:“过期”或“未过期”。 如果对过期的定时器调用了 steady_timer::wait()
函数,它将立即返回。
t.wait();
最后,在定时器到期后,我们习惯性地打印“Hello,World!” 。
完整代码如下:
#include <iostream>
#include <boost/asio.hpp>
int main()
{
boost::asio::io_context io;
boost::asio::steady_timer t(io, boost::asio::chrono::seconds(5));
t.wait();
std::cout << "Hello, world!" << std::endl;
return 0;
}
异步使用定时器
实现一个异步等待的定时器。
使用 asio 的异步功能意味着拥有一个回调函数,该函数将在异步操作完成时被调用。 在这个程序中,我们定义了一个名为 print 的函数,在异步等待完成时调用它。
void print(const boost::system::error_code& /*e*/)
{
std::cout << "Hello, world!" << std::endl;
}
int main()
{
boost::asio::io_context io;
boost::asio::steady_timer t(io, boost::asio::chrono::seconds(5));
接下来与同步方式不同的是,我们让定时器异步等待,并将上面定义的回调函数传递过去
t.async_wait(&print);
最后,我们必须要调用io_context::run()
成员函数。
因为asio保证异步回调函数只会在调用io_context::run()
函数的线程中执行。所以,不调用该函数,就永远无法取得异步函数执行结果并进行回调。
只要还有异步操作没有完成,那么io_context::run()
函数就不会返回。
完整代码如下:
#include <iostream>
#include <boost/asio.hpp>
void print(const boost::system::error_code& /*e*/)
{
std::cout << "Hello, world!" << std::endl;
}
int main()
{
boost::asio::io_context io;
boost::asio::steady_timer t(io, boost::asio::chrono::seconds(5));
t.async_wait(&print);
io.run();
return 0;
}
为Handler绑定参数
本节演示如何为Handler附加参数。
我们要实现每秒定时打印一次信息,最多打印5次。
引入头文件
#include <iostrea>
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
要实现每秒打印,那么我们就要在回调函数中更改定时器的到期时间,然后开始新的异步等待。这就意味着,在回调函数中我们要能够访问定时器对象和当前的次数,所以需要添加两个参数:
- 指向定时器对象的指针
- 当前计数值
void print(const boost::system::error_code& /*e*/,
boost::asio::steady_timer* t, int* count)
{
if (*count < 5)
{
std::cout << *count << std::endl;
++(*count);
接下来,我们将定时器的到期时间从前一个到期时间向前移动一秒。 通过旧的时间计算新的到期时间,我们可以确保计时器不会由于处理处理程序的任何延迟而偏离整秒标记。
t->expires_at(t->expiry() + boost::asio::chrono::seconds(1));
steady_timer::async_wait()
函数需要一个签名为 void(const boost::system::error_code&)
的处理函数(或函数对象),我们使用boost::bind()
函数来讲print函数转为与要求函数签名一致的函数对象。
在此示例中,boost::bind()
的 boost::asio::placeholders::error
参数是传递给处理程序的错误对象的命名占位符。 启动异步操作时,如果使用 boost::bind(),则必须仅指定与处理程序的参数列表匹配的参数。
t->async_wait(boost::bind(print,
boost::asio::placeholders::error, t, count));
}
}
完整代码如下:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
void print(const boost::system::error_code& /*e*/,
boost::asio::steady_timer* t, int* count)
{
if (*count < 5)
{
std::cout << *count << std::endl;
++(*count);
//修改过期时间
t->expires_at(t->expiry() + boost::asio::chrono::seconds(1));
//启动异步等待
t->async_wait(boost::bind(print,
boost::asio::placeholders::error, t, count));
}
}
int main()
{
boost::asio::io_context io;
int count = 0;
boost::asio::steady_timer t(io, boost::asio::chrono::seconds(1));
t.async_wait(boost::bind(print,
boost::asio::placeholders::error, &t, &count));
io.run();
std::cout << "Final count is " << count << std::endl;
return 0;
}
使用成员函数作为Handler
本节中,我们使用类的一个成员函数作为回调函数,实现与上一节相同的功能。
不同于上一节使用print函数作为回调函数,这里我们使用print类作为回调处理器。
我们将定时器与计数都封装在内部,在构造函数中就启动异步操作
class Printer
{
public:
Printer(boost::asio::io_context* ioc) : timer_(ioc)
{
timer_.async_wait(boost::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() + boost::asio::chrono::seconds(1));
timer_.async_wait(boost::bind(&Printer::print,this));
}
}
private:
boost::asio::steady_timer timer_;
int count_;
};
完整代码如下:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind/bind.hpp>
class printer
{
public:
printer(boost::asio::io_context& io)
: timer_(io, boost::asio::chrono::seconds(1)),
count_(0)
{
timer_.async_wait(boost::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() + boost::asio::chrono::seconds(1));
timer_.async_wait(boost::bind(&printer::print, this));
}
}
private:
boost::asio::steady_timer timer_;
int count_;
};
int main()
{
boost::asio::io_context io;
printer p(io);
io.run();
return 0;
}
多线程程序中的同步处理程序
我们知道调用了io_context::run()
的函数会执行回调函数。前面几节中,我们都只在一个线程中调用了该函数,因此不会并发执行。
在使用 asio 开发应用程序时,单线程方法通常是最好的方法。 缺点是它对程序(尤其是服务器)的限制,包括:
- 当处理程序可能需要很长时间才能完成时,响应能力差。
- 无法在多处理器系统上扩展。
如果您发现自己遇到了这些限制,另一种方法是让线程池调用 io_context::run()
。然而,由于这允许处理程序并发执行,当处理程序可能访问共享的、线程不安全的资源时,我们需要一种同步方法。
在上一节的基础上,我们使用两个定时器,总共输出10条信息后结束。
我们同样定义一个Printer类,将其扩展为并行运行两个定时器。
除了初始化一对 boost::asio::steady_timer
成员之外,构造函数还初始化了 strand_
成员,它是 boost::asio::strand<boost::asio::io_context::executor_type>
类型的对象。
strand类模板是一个执行器适配器,它保证通过它分派的处理程序能够在启动下一个处理程序之前完成一个正在执行的处理程序。无论调用io context::run()的线程数量如何,这都是可以保证的。当然,处理程序仍然可以与其他处理程序并发执行,这些处理程序不是通过一个链分派的,或者是通过一个不同的链对象分派的。
class printer
{
printer(boost::asio::io_context& io)
: strand_(boost::asio::make_strand(io)),
timer1_(io, boost::asio::chrono::seconds(1)),
timer2_(io, boost::asio::chrono::seconds(1)),
count_(0)
{
};
启动异步操作时,每个回调处理程序都“绑定”到一个 boost::asio::strand<boost::asio::io_context::executor_type>
对象。 boost::asio::bind_executor()
函数返回一个新的处理程序,该处理程序通过strand对象自动分派其包含的处理程序。 通过将处理程序绑定到同一条strand,我们确保它们不能同时执行。
timer1_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&printer::print1, this)));
timer2_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&printer::print2, this)));
}
~printer()
{
std::cout << "Final count is " << count_ << std::endl;
}
在多线程程序中,如果异步操作的处理程序访问共享资源,则它们应该同步。 在本教程中,处理程序(print1 和 print2)使用的共享资源是 std::cout
和count_
数据成员。
void print1()
{
if (count_ < 10)
{
std::cout << "Timer 1: " << count_ << std::endl;
++count_;
timer1_.expires_at(timer1_.expiry() + boost::asio::chrono::seconds(1));
timer1_.async_wait(boost::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() + boost::asio::chrono::seconds(1));
timer2_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&printer::print2, this)));
}
}
private:
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
boost::asio::steady_timer timer1_;
boost::asio::steady_timer timer2_;
int count_;
};
现在,主函数会导致从两个线程调用 io_context::run()
:主线程和一个附加线程。 这是使用boost::thread
对象完成的。
int main()
{
boost::asio::io_context io;
printer p(io);
boost::thread t(boost::bind(&boost::asio::io_context::run, &io));
io.run();
t.join();
return 0;
}
完整代码如下所示:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/thread/thread.hpp>
#include <boost/bind/bind.hpp>
class printer
{
public:
printer(boost::asio::io_context& io)
: strand_(boost::asio::make_strand(io)),
timer1_(io, boost::asio::chrono::seconds(1)),
timer2_(io, boost::asio::chrono::seconds(1)),
count_(0)
{
timer1_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&printer::print1, this)));
timer2_.async_wait(boost::asio::bind_executor(strand_,
boost::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() + boost::asio::chrono::seconds(1));
timer1_.async_wait(boost::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() + boost::asio::chrono::seconds(1));
timer2_.async_wait(boost::asio::bind_executor(strand_,
boost::bind(&printer::print2, this)));
}
}
private:
boost::asio::strand<boost::asio::io_context::executor_type> strand_;
boost::asio::steady_timer timer1_;
boost::asio::steady_timer timer2_;
int count_;
};
int main()
{
boost::asio::io_context io;
printer p(io);
boost::thread t(boost::bind(&boost::asio::io_context::run, &io));
io.run();
t.join();
return 0;
}