1

目的

socket_.async_read_some(boost::asio::buffer(data_),[](){
    ... });

用asio实现网络编程通常的套路是单线程异步,对特定socket发起异步的读取操作,读取ok之后马上再次发起对这个socket上的异步写入操作,反复执行; client通过传入异步函数回调函数来发起下一次的读/取操作, 而可调用对象(函数对象/lambda表达式)都可以等加成传入了一个类实例,asio负责对这个类实例存储和释放,默认的内存分配策略是operator new, 可以想象, 在持续不断的异步调用的时候,内存的分配和释放也在持续进行,降低了性能开销也产生了内存碎片. 针对这个优化点,asio提供了内存分配的hook,可以用client内存分配策略替换系统默认的行为.

实现原理

asio用于定制化内存分配策略函数如下:

asio_handler_allocate举例,my_handler就是回调对象的指针,可以看到,asio默认的内分分配策略是operator new.

class my_handler;
void* asio_handler_allocate(std::size_t size, my_handler* context) {
  return ::operator new(size);
}

如果想实现针对回调函数内存分配策略的定制,需要干两个活儿:

  1. 回调对象正常实现原本的回调操作,这部分可以通过operator ()完成,异步操作完成后就会执行真是操作.
  2. 实现asio_handler_allocateasio_handler_deallocate用于定制化内存分配策略.asio会对其进行调用.

实现解析

官方例子地址如下:Allocation

handler_allocator

自定义内存分配策略的对象,提供内存分配和释放(allocate/deallocate)的成员函数.

// class handler_allocator
// 提供字节对齐的内存地址,此空间分配在栈上, 用于提供给可调用对象实际的存储空间
typename std::aligned_storage<1024>::type storage_;
// 空间是否在使用标记位
bool in_use_;

  // 用于传递至回调函数内,以供调用,deallocate实现类似
  void* allocate(std::size_t size)
  {
    if (!in_use_ && size < sizeof(storage_))
    {
      in_use_ = true;
      return &storage_;
    }
    else
    {
      // 如果内存已经被使用,则使用默认分配策略
      return ::operator new(size);
    }
  }

custom_alloc_handler

支持内存分配策略的回调对象包装类. 注释中给出解释:

// Handler回调函数通用化,放到模板里面
template <typename Handler>
class custom_alloc_handler{
public:
    // 传入内存分配器 和 回调对象
    custom_alloc_handler(handler_allocation &a, Handler h) :
        allocator_(a),
        handler_(h) {}
    // 回调函数调用
     template <typename ...Args>
     void operator()(Args&&... args)
     {
        handler_(std::forward<Args>(args)...);
     }
};
...
private:
    // 保证所有的异步调用给回调函数分配的空间是同一份,所以声明为引用.
    handler_allocator &allocator_;
    Handler handler_;

这样,回调函数包装类就实现了对真实回调函数的封装,异步完成执行后,asio会调用包装类的operator ()实现对真实回调函数的调用. 那么这个类如何利用内存分配器提供内存分配定制呢?
按照原理章节中描述,需要实现对两个自由函数的重载,仍然以内存分配函数为例,内存释放道理一样:

  template<typename Handler>
  void* asio_handler_allocate(std::size_t size, custom_alloc_handler<Handler>* this_handler)
  {
    // 调用回调封装对象中的内存分配器,实现定制化的内分分配策略.
    return this_handler->allocator_.allocate(size);
  }

custom_alloc_handler辅助函数,用于构建实例类对象

template <typename Handler>
inline custom_alloc_handler<Handler> make_custom_alloc_handler(
    handler_allocator& a, Handler h)
{
  return custom_alloc_handler<Handler>(a, h);
}

customized Allocation使用

以上章节实际上是对client端程序员不可见的,当构建一个异步的应用程序且需要对内存分配进行控制(空间复用),那么使用方式如下:

  void do_read()
  {
    auto self(shared_from_this());
    socket_.async_read_some(boost::asio::buffer(data_),
        // 把内存分配器兑现刚和真实的回调函数(这里用lambda)封装于customer_alloc_handler中
        make_custom_alloc_handler(allocator_,
          [this, self](boost::system::error_code ec, std::size_t length)
          {
            if (!ec)
            {
              do_write(length);
            }
          }));
  }

其中,allocator为外部声明的内存分配器对象,因为需要给所有的异步调用使用,因此需要保证异步调用作用域内有效; 可以看到,对于客户端来说,可变的部分只有异步调用async_read_some的第二个参数,其他地方没有改变,allocator传递给封装类,供重载的自由内存分配函数调用, 封装类的第二个参数传入,作为异步操作结束后调用的回调函数.

参考

boost::asio sample Allocation
关于内存对齐的面试题


Ender
599 声望17 粉丝

引用和评论

0 条评论