菜鸟笔记
提升您的技术认知

brpc 笔记

bthread(一) 前言bthread(二) 线程模型及bthreadbthread(三) bthread数据结构bthread(四) bthread用户接口和代码执行路径bthread(五) 无锁队列rq的代码实现bthread(六) 小结brpc的精华bthread源码剖析brpc介绍、编译与使用brpc源码解析(一)—— rpc服务添加以及服务器启动主要过程brpc源码解析(二)—— brpc收到请求的处理过程brpc源码解析(三)—— 请求其他服务器以及往socket写数据的机制brpc源码解析(四)—— Bthread机制brpc源码解析(五)—— 基础类resource pool详解brpc源码解析(六)—— 基础类socket详解brpc源码解析(七)—— worker基于ParkingLot的bthread调度brpc源码解析(八)—— 基础类EventDispatcher详解brpc源码解析(九)—— 基础类WorkStealingQueue详解brpc源码解析(十)—— 核心组件bvar详解(1)简介和整体架构brpc源码解析(十一)—— Reducer类和Adder类解析brpc源码解析(十二)—— 核心组件bvar详解 AgentGroup类详解brpc源码解析(十三)—— 核心组件bvar详解(4)combiner详解brpc源码解析(十四)—— 核心组件bvar详解 sampler详解brpc源码解析(十五)—— bthread栈创建和切换详解brpc源码解析(十六)—— 作为client的连接建立和处理详解brpc源码解析(十七)—— bthread上的类futex同步组件butex详解brpc源码解析(十八)—— MPSC队列ExecutionQueue详解brpc源码解析(十九)—— 双buffer数据结构DoublyBufferedData详解brpc源码解析(二十)—— 用于访问下游的Channel类详解

brpc源码解析(十二)—— 核心组件bvar详解 AgentGroup类详解

阅读 : 743

上一篇从Reduce和Adder切人整体介绍了下bvar的实现机制,提到了combiner和agent,其中agent负责tls数据的管理和分配,也是bvar最核心的基本机制之一,本篇文章会根据源码介绍下agent的机制。AgentGroup类负责各个agent的分配和管理,采用了块存储。

1. 类基本定义

AgentGroup定义如下:

类名是AgentGroup,之所以叫group是因为同类型的tls数据会统一管理,模板参数是Agent,使用上传入的是AgentCombiner::Agent包装下的实际类型,比如bvar::Adder value1和bvar::Adder value2所用的会是相同实例化的类,会共用同一个tls存储数组变量。看代码的注释,brpc后续有计划让不同类型的变量也共用一个agentgroup。

类外定义了AgentId,其实就是个int,用来标识变量、在存储块中定位变量位置的,下面会详细介绍。

2. 成员变量

首先是有两个const static的关于每个block存多少个元素的变量,RAW_BLOCK_SIZE和ELEMENTS_PER_BLOCK,根据赋值我们可以知道,如果Agent类型大于等于4096,那么每个块就一个元素,再比如如果Agent类型占1024,那么每块的元素会是4个。

另外还有四个static变量,因为是static变量模板参数相同的实例都会共用。

(1)_s_mutex是新建和销毁agent要用到的锁。
(2)_s_agent_kinds是当前agentgroup(Agent参数相同)的agent数量,同时也用于构造agentId。
(3)_s_free_ids是个deque的指针,保存了空闲的agentId用于再分配。
(4)_s_tls_blocks由__thread修饰,tls变量,是个vector的指针,这个vector保存的则是ThreadBlock的指针,这也是agent的核心变量,指向每个thread的tls数据块。

ThreadBlock是内部struct,则是真正的tls存储类型,由ELEMENTS_PER_BLOCK大小的数组_agents和一个取指定偏移量位置变量的at函数组成,用BAIDU_CACHELINE_ALIGNMENT修饰是为了对齐cache line避免cache bouncing。

3. 函数

3.1 Private函数

Private函数有两个,都比较简单,如下:
(1)static void _destroy_tls_blocks()
析构_s_tls_blocks里的元素并delete _s_tls_blocks本身,用于线程退出的时候清除tls存储。

(2)inline static std::deque &_get_free_ids()
获取空闲的已有id,用deque保存我理解是因为它resize比较高效。

3.1 Public的函数

public函数主要是供combiner调用,如下:
(1)inline static AgentId create_new_agent()

根据名称就知道,这是新建agent的,返回的是新建agent的id,这个函数由combiner的构造函数调用,这个函数比较简单,就是加锁后先判断是否有空闲的id(包括原来分配的tls存储),有就直接返回老的,否则返回_s_agent_kinds的值并对_s_agent_kinds自增,也就是新建了一个id,这里说的新建仅仅是新建id,并没有分配空间构造变量,如果是已有的id则是原来已经构造好的。

(2)inline static int destroy_agent(AgentId id)

销毁agent,这里说的销毁也可以理解成归还,并没有delete建立的对象,后续还可以重用,combiner的析构函数会调用,比如一个bvar析构了,对应的combiner析构了也就会归还agentid。

(3)inline static Agent* get_tls_agent(AgentId id)

根据id拿到agent指针,如果block还没分配直接返回NULL,id除以perlock得到block_id(定位到block),再根据块内偏移id-blockid*elements_per_block定位到具体数据,注意注释里描述的,不存在的id可能返回非null,是因为id和对应的数据是可以重用的,比如id=10的归还了,但因为10所指向的数据块还在,这个时候用如果用10来取仍然能取到,不过实际使用中没啥影响。

(4)inline static Agent* get_or_create_tls_agent(AgentId id)

和get_tls_agent类似,根据id拿到agent指针,但如果block还没分配会进行分配,之所以分成了两个函数是为了让get_tls_agent的部分实现尽可能的快。Combiner在调用的时候会先调AgentGroup::get_tls_agent(_id),如果为null再调AgentGroup::get_or_create_tls_agent(_id)。容易理解实际使用中get_tls_agent就能返回非NULL的占比很大。和get_tls_agent的主要区别在于多了_s_tls_blocks为null和(*_s_tls_blocks)[block_id]为null的时候的空间分配,也就是or_create的含义。注意中间的butil::thread_atexit(_destroy_tls_blocks),线程退出的时候调用上面说的private的_destroy_tls_blocks函数。

4. 总结

AgentGroup负责tls数据的分配和获取,相同类型的会共用一个块存储数组,设计宗旨是能够让调用方尽可能快地获取到某个bvar变量的agent。后面将会解析combiner源码,会进一步介绍combiner是如何使用AgentGroup的。