上一篇从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的。

Logo

更多推荐