利用计时器了解Boost Asio

文章目录


参考链接

一、同步使用计时器

首先要包含相关头文件,其中要使用asio库,这个头文件是必不可少的:

cpp 复制代码
#include <boost/asio.hpp>

之后要创建一个上下文模块io_context,如果读者研究过muduo库,或者libevent这些库的话,可以理解为这个io_context内部就是类似与封装了一个epool(Linux下,Windows是iocp),然后执行run之后就不断地对设置的需要监听的事件进行监听,一旦事件可以进行操作,例如可读,可写,就去调用设置好的回调函数进行处理。

所以传入上下文的过程就类似于添加要监控的事件,上下文run的过程就类似于开个循环,一直检测事件是否就绪,并执行对应的任务。当然实际上的上下文肯定不会是这里说的这么简单,这里只是为了好理解才这样表述的。

cpp 复制代码
boost::asio::io_context ioc;

之后创建一个定时器,第一个参数是一个上下文,第二个参数是一个时间。执行完构造函数之后类似于把这个定时事件进行监控,添加到epool里,一旦事件就绪,即时间到了,就完成对应操作。

cpp 复制代码
boost::asio::steady_timer t(ioc, boost::asio::chrono::seconds(5));

这里调用wait,是同步的,所以调用wait之后会阻塞当前程序,直到定时器触发,然后输出hello world

cpp 复制代码
t.wait();
std::cout << "Hello, world!" << std::endl;

完整代码:

cpp 复制代码
#include <iostream>
#include <boost/asio.hpp>
int main()
{
  boost::asio::io_context ioc;

  boost::asio::steady_timer t(ioc, boost::asio::chrono::seconds(5));
  t.wait();

  std::cout << "Hello, world!" << std::endl;

  return 0;
}

二、异步使用定时器

也是先创建一个上下文,然后创建一个定时器,这里我们调用async_wait,这就是异步处理,程序执行完这句之后,会直接往下执行,不会阻塞,一旦时间到了之后就会调用已经设置好的回调函数。

所以在调用async_wait的时候需要传入回调函数,这里定时器的回调函数默认有一个参数是const boost::system::error_code&,这个不需要我们手动传入,asio内部在执行这个回调函数的时候会自动传参。

cpp 复制代码
void print1(const boost::system::error_code& error)
{
  std::cout << "Hello, world!" << std::endl;
}
t.async_wait(print1);

之后调用ioc.run()让程序跑起来就行了

完整代码:

cpp 复制代码
#include <iostream>
#include <boost/asio.hpp>
void print1(const boost::system::error_code& error)
{
  std::cout << "Hello, world!" << std::endl;
}

int main()
{
  boost::asio::io_context ioc;

  boost::asio::steady_timer t(ioc, boost::asio::chrono::seconds(5));
  t.async_wait(print1);

  ioc.run();

  return 0;
}

三、绑定参数给回调函数

上面第二个例子回调函数是只有一个参数error的,而且这个参数是程序自己处理的,那有些情况我们需要自己往回调函数里传递一些参数,那怎么办呢?答案是使用bind。

下面这个例子我们让回调函数多了俩参数,一个是定时器的指针,另一个是一个计数器的指针,由于默认error是第一个参数,所以我们一半把自己的参数写在第二个参数及之后。

前几步都和前面一样不赘述

cpp 复制代码
  boost::asio::io_context ioc;

  int count = 0;
  boost::asio::steady_timer t(ioc, boost::asio::chrono::seconds(1));

后面调用async_wait的时候需要用bind处理一下。

cpp 复制代码
t.async_wait(std::bind(print, std::placeholders::_1, &t, &count));

第一个参数是error,需要asio内部自己传递,所以我们用一个占位符std::placeholders::_1进行占位,它内部传error的时候自己传递到占位符内个位置就好了,后面我们就可以传递自己设置的两个参数了。

这个回调函数的含义是,第一次时间到了之后执行这个回调,计数器<5,那就打印一下计数器的值,然后把计时器重新加一秒,然后重新async_wait,那1s过后就又会执行这个回调,又会重置过期时间...最后知道计数器不再<5就会退出。

cpp 复制代码
void print(const boost::system::error_code& err,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(std::bind(print, std::placeholders::_1, t, count));
  }
}

完整代码:

cpp 复制代码
#include <iostream>
#include <functional>
#include <boost/asio.hpp>

void print(const boost::system::error_code& err,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(std::bind(print, std::placeholders::_1, t, count));
  }
}

int main()
{
  boost::asio::io_context ioc;

  int count = 0;
  boost::asio::steady_timer t(ioc, boost::asio::chrono::seconds(1));
  t.async_wait(std::bind(print, std::placeholders::_1, &t, &count));

  ioc.run();

  std::cout << "Final count is " << count << std::endl;

  return 0;
}

四、成员函数作为回调函数

这里回调函数变成了成员函数,回调函数里使用的计数器和定时器也都成了成了成员变量了,所以也就不需要传递的,但是成员函数默认第一个参数是this指针,所以在bind的时候我们需要把this指针传进去。

并且bind第一个参数需要&类名::成员函数名,才行,不了解bind使用的可以先去学一下。然后用一个占位符留一个位置给error传递就行。

cpp 复制代码
_timer.async_wait(std::bind(&Printer::print, this, std::placeholders::_1));

此外构造函数要用引用的方式接受上下文,保证初始化列表里定时器传入的上下文和main函数里的上下文是用一个上下文。

cpp 复制代码
 Printer(boost::asio::io_context &ctx)

其余流程几乎和第三个没有区别,不再赘述。

完整代码:

cpp 复制代码
#include <iostream>
#include <boost/asio.hpp>
#include <functional>
class Printer
{
public:
    Printer(boost::asio::io_context &ctx) : _timer(ctx, boost::asio::chrono::seconds(1)),
                                            _count(0)
    {
        _timer.async_wait(std::bind(&Printer::print, this, std::placeholders::_1));
    }

    ~Printer()
    {
        std::cout << "Final count is " << _count << std::endl;
    }
    // 内部会自己传递一个error参数s
    void print(const boost::system::error_code &ec)
    {
        if (!ec)
        { // 只有成功时才继续
            if (_count < 5)
            {
                ++_count;
                std::cout << _count << std::endl;
                _timer.expires_at(_timer.expiry() + boost::asio::chrono::seconds(1));
                _timer.async_wait(std::bind(&Printer::print, this, std::placeholders::_1));
            }
        }
        else
        {
            std::cerr << "Error or cancellation: " << ec.message() << std::endl;
        }
    }

private:
    int _count;
    boost::asio::steady_timer _timer;
};

int main4()
{
    boost::asio::io_context ctx;
    Printer p(ctx);
    ctx.run();
    return 0;
}

五、多线程中同步运行处理函数

前置内容,帮助理解:

在异步情况下,我们会设置回调函数。在asio中会有一个线程安全的队列,当某些事件就绪之后,就会把对应事件的回调函数放到这个队列里,然后每次去这个队列里取一个回调函数进行处理。

在单线程情况下,也就是io_context::run() 在单个线程中运行。所有回调按顺序从队列中取出并执行,不会并发处理。无需额外同步,因为只有一个线程在处理回调。

但是在多线程情况下,多个线程调用 io_context::run(),多个线程可以同时调用 io_context::run(),此时:Asio 内部使用线程安全的队列存储就绪的回调。多个线程可以并发地从队列中取出回调并执行。

但需要注意:回调本身的执行是线程安全的,但回调内部访问的共享数据需要用户自行同步(如用 std::mutex)。

这样可能就会导致某一时刻有俩线程同时在执行两个不同的回调函数,但这俩回调函数内部对同一个全局变量进行操作了,这样就会导致数据不一致问题,所以需要我们自己进行加锁解决这个问题,但Asio中提供了一个方法,可以让回调函数即使在多线程中也串行执行,从而避免数据不一致问题。

就是:boost::asio::strand<boost::asio::io_context::executor_type> _strand;

boost::asio::strand 可以强制一组回调串行化执行,即使有多线程。
strand需要一个执行器进行初始化,这个执行器可以从上下文io_context中获取,第二个参数也是个回调函数

在async_wait的时候需要使用boost::asio::bind_executor(strand, print1)把对应的回调函数设置到strand里,设置到strand里的回调函数就会保证他们不会同时执行。

cpp 复制代码
boost::asio::io_context io;
boost::asio::strand<boost::asio::io_context::executor_type> strand(io.get_executor());

// 通过 strand 包装回调,确保同一 strand 的回调按顺序执行
t.async_wait(boost::asio::bind_executor(strand, print1));

这里把print1和print2都设置到_strand上了,所以不会造成数据不一致问题。

cpp 复制代码
 _timer1.async_wait(boost::asio::bind_executor(_strand, std::bind(&printer::print1, this, std::placeholders::_1)));
 _timer2.async_wait(boost::asio::bind_executor(_strand, std::bind(&printer::print2, this, std::placeholders::_1)));
cpp 复制代码
#include <iostream>
#include <boost/asio.hpp>
#include <functional>
#include <thread>
class printer
{
public:
    printer(boost::asio::io_context &ioc)
        : _strand(boost::asio::make_strand(ioc)), _timer1(ioc, boost::asio::chrono::seconds(1)), _timer2(ioc, boost::asio::chrono::seconds(1)), _count(0)
    {
        _timer1.async_wait(boost::asio::bind_executor(_strand, std::bind(&printer::print1, this, std::placeholders::_1)));
        _timer2.async_wait(boost::asio::bind_executor(_strand, std::bind(&printer::print2, this, std::placeholders::_1)));
    }
    ~printer()
    {
        std::cout << "Final count is " << _count << std::endl;
    }
    void print1(const boost::system::error_code &err)
    {
        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, std::bind(&printer::print1, this,std::placeholders::_1)));
        }
    }
    void print2(const boost::system::error_code &err)
    {
        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, std::bind(&printer::print2, this, std::placeholders::_1)));
        }
    }

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 ioc;
    printer p(ioc);
    std::thread t([&]{ioc.run();});
    ioc.run();
    t.join();
    return 0;
}

六、完整代码与CMake

想测试哪个,把main函数名修改一下,重新编译就行。

cpp 复制代码
#include <iostream>
#include <boost/asio.hpp>
#include <functional>
#include <thread>

int main1()
{
  boost::asio::io_context ioc;

  boost::asio::steady_timer t(ioc, boost::asio::chrono::seconds(5));
  t.wait();

  std::cout << "Hello, world!" << std::endl;

  return 0;
}
void print1(const boost::system::error_code& /*e*/)
{
  std::cout << "Hello, world!" << std::endl;
}

int main2()
{
  boost::asio::io_context ioc;

  boost::asio::steady_timer t(ioc, boost::asio::chrono::seconds(5));
  t.async_wait(&print1);

  ioc.run();

  return 0;
}
void print(const boost::system::error_code& err,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(std::bind(print, std::placeholders::_1, t, count));
  }
}

int main3()
{
  boost::asio::io_context ioc;

  int count = 0;
  boost::asio::steady_timer t(ioc, boost::asio::chrono::seconds(1));
  t.async_wait(std::bind(print, std::placeholders::_1, &t, &count));

  ioc.run();

  std::cout << "Final count is " << count << std::endl;

  return 0;
}
class Printer
{
public:
    Printer(boost::asio::io_context &ctx) : _timer(ctx, boost::asio::chrono::seconds(1)),
                                            _count(0)
    {
        _timer.async_wait(std::bind(&Printer::print, this, std::placeholders::_1));
    }

    ~Printer()
    {
        std::cout << "Final count is " << _count << std::endl;
    }
    // 内部会自己传递一个error参数s
    void print(const boost::system::error_code &ec)
    {
        if (!ec)
        { // 只有成功时才继续
            if (_count < 5)
            {
                ++_count;
                std::cout << _count << std::endl;
                _timer.expires_at(_timer.expiry() + boost::asio::chrono::seconds(1));
                _timer.async_wait(std::bind(&Printer::print, this, std::placeholders::_1));
            }
        }
        else
        {
            std::cerr << "Error or cancellation: " << ec.message() << std::endl;
        }
    }

private:
    int _count;
    boost::asio::steady_timer _timer;
};

int main4()
{
    boost::asio::io_context ctx;
    Printer p(ctx);
    ctx.run();
    return 0;
}

class printer
{
public:
    printer(boost::asio::io_context &ioc)
        : _strand(boost::asio::make_strand(ioc)), _timer1(ioc, boost::asio::chrono::seconds(1)), _timer2(ioc, boost::asio::chrono::seconds(1)), _count(0)
    {
        _timer1.async_wait(boost::asio::bind_executor(_strand, std::bind(&printer::print1, this, std::placeholders::_1)));
        _timer2.async_wait(boost::asio::bind_executor(_strand, std::bind(&printer::print2, this, std::placeholders::_1)));
    }
    ~printer()
    {
        std::cout << "Final count is " << _count << std::endl;
    }
    void print1(const boost::system::error_code &err)
    {
        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, std::bind(&printer::print1, this,std::placeholders::_1)));
        }
    }
    void print2(const boost::system::error_code &err)
    {
        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, std::bind(&printer::print2, this, std::placeholders::_1)));
        }
    }

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 ioc;
    printer p(ioc);
    std::thread t([&]{ioc.run();});
    ioc.run();
    t.join();
    return 0;
}

对应CMake

cpp 复制代码
cmake_minimum_required(VERSION 3.10)

project(BoostStudy)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

add_executable(std study.cc)

target_link_libraries(
    std
    PRIVATE
    pthread
    boost_system)

目录结构:

相关推荐
青草地溪水旁5 小时前
设计模式(C++)详解——代理模式 (Proxy Pattern)(1)
c++·设计模式·代理模式
小年糕是糕手5 小时前
【C语言】C语言预处理详解,从基础到进阶的全面讲解
linux·c语言·开发语言·数据结构·c++·windows·microsoft
澄澈i5 小时前
CMake学习篇[2]---CMake进阶+非同级目录构建+静态库/动态库链接
c++·学习·cmake
起个名字费劲死了5 小时前
Pytorch Yolov11 OBB 旋转框检测+window部署+推理封装 留贴记录
c++·人工智能·pytorch·python·深度学习·yolo·机器人
高山有多高6 小时前
从 0 到 1 保姆级实现C语言双向链表
c语言·开发语言·数据结构·c++·算法·visual studio
aluluka6 小时前
Emacs 折腾日记(三十)——打造C++ IDE 续
c++·ide·emacs
半桔6 小时前
【网络编程】UDP 编程实战:从套接字到聊天室多场景项目构建
linux·网络·c++·网络协议·udp
Lucis__6 小时前
C++相关概念与语法基础——C基础上的改进与优化
c语言·开发语言·c++