2021SC@SDUSC
一、AgentGroup类简介
紧接上文,我们介绍了Reducer类及其子类,这其中就提到了线程的agent概念以及其中的get_or_create_tls_agent。所以这篇文章我们介绍agent的概念及AgentGroup类,agent可以中文翻译为代理人,在这里它负责TLS(Thread Local Storage)的分配和管理,AgentGroup类主要负责agent的创建和管理,按块存储agent。
二、代码分析
同样首先分析一下它的属性。首先是两个const静态变量,这两个变量对应了上面我说的按块存储agent。一块的大小即RAW_BLOCK_SIZE为4096。ELEMENT_PER_BLOCK即每个块中放多少个agent取决于agent的大小,这里ELEMENT_PER_BLOCK的计算方法采用的是上取整而没有下取整,因为当一个块不满足全部存储需求时是依旧可以接受的(否则大于4096甚至得到0,起初一直没理解这部分运算的含义。它并不是一个严格的限制,而是一个人为的宽泛的规定,目的是按块分配,给定的SIZE方便算出偏移量)。
接下来还有四个静态变量,代表多个AgentGroup将共同使用这四个变量。
_s_mutex不需多说,是前面也使用过的信号量,看来在brpc中处理线程临界区问题的方法都是这个。
_s_agent_kinds记录当前AgentGroup(Agent参数相同)的agent数量。
_s_free_ids是个deque双向队列的指针。这个双向队列的作用马上会介绍。
_s_tls_blocks是ThreadBlock指针变量的vector数组,指向各个线程的TLS数据块。
还有上面说到的ThreadBlock结构体。我们看下它内部并不复杂的构造,首先是ELEMENTS_PER_BLOCK大小的数组_agents,它相当于根据上面的计算分配好了大小,还有一个at方法,给定偏移量offset去返回定位到的Agent。struct后面的BAIDU_CACHELINE_ALIGNMENT是为了解决多线程的伪共享问题,即实现cacheline对齐(这个问题在./butil/macros.h中被实现解决,感兴趣的朋友可以自行阅读)。
了解了这些属性,我们接下来分析一下它的方法。
_get_free_ids()是该类中最重要的一个方法,也是返回我们上面说到的双向队列,deque保存了空闲的AgentId。当调用这个方法,即返回所有可以再被使用的AgentId。
然后首先看到创建和删除agent的操作。关于_s_free_ids的使用再开始就要加锁。获取访问权限后,先看_s_free_ids是否为空,即是不是还有可用的AgentId,如果有就拿出最后一个Id给当前的new_agent使用。如果没有,那么代表当前AgentGroup中所有Agent数量的值就要自增,赋给当前agent一个全新的Id。
然后是删除agent的操作。其实很简单,只需要将它的Id再还回去,在程序中,它的Id已经是空闲可重复使用的了。
增删看完后,分析一下访问操作。get_tls_agent我们在之前的文章中用到过。根据传入的Id,除以每块多少个agent下取整得到该Id所处的块block_id。如果这个block_id合法的话,去定位到这个地方。同时结合上述的删除操作我们也会发现,一个agent即使被删除,我们通过这个Id依然可以定位到它的数据,也就是说对于不存在的Id返回非空值。这是有意为之,因为这个函数调用非常频繁,所以要考虑最快的处理时间,这个“遗漏”不会出现问题。后续的时候会填上这个问题的坑。
还有一个和它非常接近的函数get_or_create,即它多出了创建agent的功能,之所以这样分开,也是为了加快get_tls_agent的处理速度,是效率的考量。所以在这个函数我们只看它是如何create的就好了,下半部分if(tb==NULL)判断是否该Id返回空值,返回之后则new一个ThreadBlock,同时在指针vector数组里增加这个指向。
最后还有一个块的删除操作,只需要遍历ThreadBlock指针数组进行delete操作即可。