上一篇从reduce和adder切人整体介绍了下bvar的实现机制,提到了combiner和agent,其中agent负责tls数据的管理和分配,也是bvar最核心的基本机制之一,本篇文章会根据源码介绍下agent的机制。agentgroup类负责各个agent的分配和管理,采用了块存储。
agentgroup定义如下:
类名是agentgroup,之所以叫group是因为同类型的tls数据会统一管理,模板参数是agent,使用上传入的是agentcombiner::agent包装下的实际类型,比如bvar::adder value1和bvar::adder value2所用的会是相同实例化的类,会共用同一个tls存储数组变量。看代码的注释,brpc后续有计划让不同类型的变量也共用一个agentgroup。
类外定义了agentid,其实就是个int,用来标识变量、在存储块中定位变量位置的,下面会详细介绍。
首先是有两个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.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函数。
agentgroup负责tls数据的分配和获取,相同类型的会共用一个块存储数组,设计宗旨是能够让调用方尽可能快地获取到某个bvar变量的agent。后面将会解析combiner源码,会进一步介绍combiner是如何使用agentgroup的。