boost.Asio Example定时器的思考---结果阻碍了我们对本质的思考

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中过程操作顺序的,所以两个异步事件共享数据我们还是要加锁或者其他方式控制的,虽然有的实现方式不是用线程,但是还是大同小异的,为了保障程序的正确运行我们还是要不依赖于实现,而是依赖于逻辑!

猜你喜欢

转载自blog.csdn.net/RGBMarco/article/details/81003239