boost.Asio官网给的教程很多关于定时器的例子,现在我就来研究下这几个例子
Example 1:
//
// timer.cpp
// ~~~~~~~~~
//
// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <iostream>
#include <asio.hpp>
int main()
{
asio::io_context io;
asio::steady_timer t(io, asio::chrono::seconds(5));
t.wait();
std::cout << "Hello, world!" << std::endl;
return 0;
}
第一个例子很简单,主要向我们介绍同步的概念,先是设定了一个定时器然后等待5秒啥事也不干,然后输出Hello,World,第一个例子仿佛是先吹再黑,你看把单线程下不用异步,搞半天才输出个Hello,World,比如网络中你给每个人连接上的人发送Hello,World,而处理第一个连接后你就让整个程序等5秒然后发送Hello,World,然后处理第二个连接时,又是这样,这样必然浪费资源
怎么办呢?接下来官网给了第二个例子
//
// timer.cpp
// ~~~~~~~~~
//
// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <iostream>
#include <asio.hpp>
void print(const asio::error_code& /*e*/)
{
std::cout << "Hello, world!" << std::endl;
}
int main()
{
asio::io_context io;
asio::steady_timer t(io, asio::chrono::seconds(5));
t.async_wait(&print);
io.run();
return 0;
}
这里介绍了异步的概念虽然结果和第一个例子结果一致,但是思想却不同,
第一种呢是你中午睡觉(你这人太累,睡觉就像死了一样,不做梦),睡觉之后然后去干活
第二种是你饱暖思淫欲,睡觉还要做梦.然后去干活
第一种可以看作是对整个过程的阻塞,而第二种是对过程中的一部分阻塞,但不影响其他过程的顺序执行(除非其他过程部分等待异步结束)
下面来讨论第三个例子
//
// timer.cpp
// ~~~~~~~~~
//
// Copyright (c) 2003-2018 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <iostream>
#include <asio.hpp>
#include <boost/bind.hpp>
void print(const asio::error_code& /*e*/,
asio::steady_timer* t, int* count)
{
if (*count < 5)
{
std::cout << *count << std::endl;
++(*count);
t->expires_at(t->expiry() + asio::chrono::seconds(1));
t->async_wait(boost::bind(print,
asio::placeholders::error, t, count));
}
}
int main()
{
asio::io_context io;
int count = 0;
asio::steady_timer t(io, asio::chrono::seconds(1));
t.async_wait(boost::bind(print,
asio::placeholders::error, &t, &count));
io.run();
std::cout << "Final count is " << count << std::endl;
return 0;
}
这只是个每秒输出一个数的例子,非常简单,只是为了熟悉一下API
第四个例子也是这样
下面重点是第五个例子,为了直观的输出,我对例子输出作了修改加了点提示
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
using namespace boost;
class printer {
public:
printer(asio::io_context& io) :strand_(io),
timer1_(io,asio::chrono::seconds(1)),timer2_(io,asio::chrono::seconds(1)),count_(0){
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;
}
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::steady_timer timer1_;
asio::steady_timer timer2_;
int count_;
asio::io_context::strand strand_;
};
int main() {
asio::io_context io;
printer p(io);
asio::detail::thread t(boost::bind(&asio::io_context::run,&io));
io.run();
t.join();
return 0;
}
这里用了线程,和asio的asio::io_context::strand,这个鬼东西是干嘛的,我们观察这个程序的输出
貌似我们不用asio::io_context::strand这东西也能做到
#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
using namespace boost;
class printer {
public:
printer(asio::io_context& io):timer1_(io,asio::chrono::seconds(1)),timer2_(io,asio::chrono::seconds(1)),count_(0){
timer1_.async_wait(bind(&printer::print1,this));
timer2_.async_wait(bind(&printer::print2,this));
}
private:
void print1() {
if (count_ < 10) {
std::cout << "TimerId: timer 1" << " " << "count: " << count_ << std::endl;
++count_;
timer1_.expires_at(timer1_.expiry() + asio::chrono::seconds(1));
timer1_.async_wait(bind(&printer::print1,this));
}
}
void print2() {
if (count_ < 10) {
std::cout << "TimerId: timer 2" << " " << "count: " << count_ << std::endl;
++count_;
timer2_.expires_at(timer2_.expiry() + asio::chrono::seconds(1));
timer2_.async_wait(bind(&printer::print2,this));
}
}
int count_;
asio::steady_timer timer1_;
asio::steady_timer timer2_;
};
int main() {
asio::io_context io;
printer p(io);
io.run();
return 0;
}
运行发现和它也没差异
那么引入asio::io_context::strand的目的是干嘛的呢
英语上是搁浅的意思,作用就是在所有注册的回调函数都不能同时运行,就是封装所有回调函数是原子性的,那我没用这个东西却保证了相同结果的有什么局限性呢,我认为局限性有两点,
第一:没使用strand时间可能会长一点(当然这不是使用strand)的主要原因,据说strand内部实现并未用锁,使用strand使得颗粒度减小,通过隔离过程来保障数据安全(如果内部用了锁,那还不如用来锁数据)
第二点:异步的实现,这是最重要的,我写的代码其实是有逻辑错误的,Timer 1和Timer 2都使用了同一个数据count_,不仅读取还进行了修改,我们注册了两个异步事件,然而我们却没有保障事件A在改count_时,事件B不在读取,有人可能说这是单线程不可能出现这种情况,但是我们用的别人写函数,而且系统还有调度问题,异步只是一种表面的结果,我们完全可以使用线程来模拟异步,A事件用线程A,B事件用线程B就会出现问题了,因为异步A和异步B是没有隔绝的,异步的概念是不能确保A中过程和B中过程操作顺序的,所以两个异步事件共享数据我们还是要加锁或者其他方式控制的,虽然有的实现方式不是用线程,但是还是大同小异的,为了保障程序的正确运行我们还是要不依赖于实现,而是依赖于逻辑!