记录一下在读brpc的时候学习到的点
ResourcePool
ResourceID来标识资源。虽然不同的类型有不同类型的ResourcID,但是内部都是一个uint64
存储数据的是Block。当线程需要内存的时候,他就会分配一个block。每个block内部的数据只会被一个线程使用
一个block就相当于是一个小的DataPool
若干个block组成一个block group。BlockGroup通过指针数组索引block
LocalPool是thread local的data pool
一个比较有意思的点就是这里定义的一个宏 BAIDU_RESOURCE_POOL_GET。这里注释说的比较清楚,不同类型的构造是不同的。对于POD来说,new T
和new T()
不同,因为后者是会把值都初始化为0
LocalPool保存了全局资源池的指针,以及FreeChunk,代表的是指向当前空闲的Chunk的Id
获取资源的时候就会先尝试从FreeChunk中取一个。thread local就不会出现竞争,所以我们也不需要锁。本地的free chunk没有的话就从全局的资源池中取一个free chunk
T* p = new ((T*)_cur_block->items + _cur_block->nitem) T CTOR_ARGS;
这句貌似是所谓的placement new,就是只构造,不负责申请空间
free的时候会优先push到本地的free chunk中,本地的free chunk满了再去push给全局的free chunk
如果free chunk中没有的话,就从block中申请一个新的item返回
我们可以通过id来标识唯一的一个resource,即索引block group,索引block,再索引block内item
全局的resource pool是一个单例。比较有意思的是由于我们希望并发访问,但是还希望有懒汉式单例。所以就通过原子变量加载这个指针。如果发现没有的话就上锁,然后构造一个资源池,并存入到原子变量中。
add_block会申请一个新的block,并通过原子指令lock-free的append到block的最后面。add_block_group也是同理,不同的是这里的block group是通过锁来保护的。我感觉主要是因为block group比较大,不会频繁的创建,而且也不是必须创建的。如果用原子指令的话可能就会创建很多无用的block group,而这里通过锁我们保证只会创建一个block group。(我在想是不是可以记录当前的block group number,然后原子加,只有得到block group number = old number + 1的线程才能申请block group。但是这样的话如果这时候这个线程被换走了,那么其他的人也无法make progress,因为这个操作相当于一次上锁了,并且os还无法感知,相当于是linus说的那种用户态spinlock)看起来具体的场景还是要具体的分析,不能一味的去追求lock-free什么的。这里我们需要保证每个thread都申请到一个block,所以可以用简单的原子加来获得自己的block index。但是block group是共享的,一个人创建就足够,所以我们在临界区内创建。
在pop_free_chunk和push_free_chunk中都用了pthread mutex,看起来mutex还是比较好用的
通过引用计数来清理全局的pool。每当一个local pool被析构,我们会减少1的引用计数。当数量为0的时候就代表我们可以尝试释放全局pool。但是实际上这些内存可能还在别的地方被使用(我猜测可能是转移了内存的所有权?不然的话线程都退出了还有谁用呢?)
那么为什么要设置block和block group呢?我的理解是类似操作系统的多级页表
通过ResourceID来标识资源,并且尽可能的复用。虽然感觉整体没有很亮眼的地方(资源池也就这么实现了),但是代码写的是真的漂亮。
文章评论