代码改变世界

C++ 异步编程:Boost.Asio

2019-12-03 20:42  陈心朔  阅读(6210)  评论(0编辑  收藏  举报

Boost.Asio 是一个用于网络和低级 I/O 编程的跨平台 C++ 库,它使用现代 C++ 方法为开发人员提供一致的异步模型


一个异步使用计时器的样例

#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 me;     // 提供对 i/o 功能的访问
    boost :: asio :: steady_timer t(io,boost :: asio :: chrono :: seconds(5));
    t.async_wait(print);        // 插入回调函数
    io.run();
 
 
    return;
}

asio 库提供了一种保证,即只能从当前调用 io_context::run() 的线程调用回调处理程序

io_context::run() 函数将继续运行,它的工作是计时器上的异步等待,在计时器到期并且回调完成之前调用不会返回

在调用 io_context::run() 前需要给 io_context 设定一些工作,如果省略了 t.async_wait(print); 的调用,io_context::run() 将立刻返回

 

多次触发计时器

要实现重复计时器,需要在回调函数中更改计时器的到期时间,然后启动新的异步等待

设定计时器将在第 6 次触发时停止程序

#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
 
// 增加指向计时器对象的指针以访问计时器对象,以及一个计数器 count
void print(const boost::system::error_code & /*e*/,
    boost::asio::steady_timer * t, int * count)
{
    // 这里没有明确的调用要求 io_context 停止,而是通过 count == 5 时不再对计时器启动新的异步等待,io_context 将结束工作并停止运行
    if (*count < 5)
    {
        std::cout << *count << std::endl;
        ++(*count);
        // 将计时器到期时间向后延时 1 秒
        t->expires_at(t->expiry() + boost::asio::chrono::seconds(1));
        // 启动一个新的异步等待,使用 boost::bind 使需要指定与回调函数参数列表相匹配的参数
        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;
}

 

使用类的成员函数作回调处理

#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
 
 
class printer
{
public:
    // 构造时引用 io_context 对象,使用它初始化 timer
    printer(boost::asio::io_context& io)
    : timer_(io, boost::asio::chrono::seconds(1)),
      count_(0)
    {
        // 使用 bind 绑定当前对象的 this 指针,利用成员 count 控制计时器的执行
        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()
{
    // main 里的调用简单了很多
    boost::asio::io_context io;
    printer p(io);
    io.run();
 
    return 0;
}

 

多线程中的回调函数同步

asio 库提供了一种保证,即只能从当前调用 io_context::run() 的线程调用回调函数。因此,仅从一个线程调用 io_context::run() 回调函数无法并发运行

这里需要使用 io_context::strand 来控制分配 handler 的执行,它可以保证无论 io_context::run() 的线程有多少,同时只能有一个 handler 程序执行,保证了共享资源的线程安全

在本例中 handler (print1 / print2) 所访问的共享资源包括 std::cout 和成员变量 count_

#include <iostream>
#include <boost/asio.hpp>
#include <boost/thread/thread.hpp>
#include <boost/bind.hpp>
 
 
class printer
{
public:
     printer(boost::asio::io_context& io)
    : strand_(io),  // strand 成员,用于控制 handler 的执行
      timer1_(io, boost::asio::chrono::seconds(1)), // 运行两个计时器
      timer2_(io, boost::asio::chrono::seconds(1)),
      count_(0)
    {
        // 启动异步操作时,每个 handler 都绑定到 strand 对象
        // bind_executor() 返回一个新的 handler,它将自动调度其包含的 printer::print1
        // 通过将 handler 绑定到同一个 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;
    }
     
    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::io_context::strand strand_;
    boost::asio::steady_timer timer1_;
    boost::asio::steady_timer timer2_;
    int count_;
};
 
 
int main()
{
    boost::asio::io_context io;
    printer p(io);
    // main 函数现在有两个线程调用 io_context::run(),主线程和由 boost::thread 对象执行的额外线程
    boost::thread t(boost::bind(&boost::asio::io_context::run, &io));
    io.run();
    t.join();
 
    return 0;
}

 


参考:

Boost.Asio

How strands work and why you should use them