2021SC@SDUSC BRPC代码分析(八) —— butil-resource pool类详解

2021SC@SDUSC


一、resource pool类简介

        上篇文章简单介绍了bthread的概念及作用,并且分析了同步的工具——butex,这篇文章继续介绍一个bthread的基础类,resource pool。
        resource pool是非常基础且重要的底层工具类之一,中文翻译过来就是资源池,是一个用于在多线程环境下进行资源分配和回收的类,也可以理解将它的功能为高度竞争环境下速度更快的new和delete。resource pool在brpc里面使用很广泛,在这里介绍主要是因为它用在了bthread的taskmeta分配(因为taskmeta对于创建的速度有着很高的要求),所以去看了这个基础类,我们可以从这里看到一些brpc对性能的追求,而且因为需要支持各种类型,这个类也很好地展现了c++模板的使用。

二、代码分析

resource pool的主要作用就是资源分配,可以看到最常用的函数当然是分配和回收资源,直接供外部调用的函数如下。
get_resource和new类似,是获取对象的,这边提供了三个方法重载,分别对应没有参数、1个参数和2个参数构造对应类型对象的情况,内部则都是调用ResourcePool::singleton()->get_resource。
在这里插入图片描述
在这里插入图片描述
调用的是ResourcePool::singleton()是获取对应类型资源池的单例,然后再从中分配回收资源。在这里插入图片描述
_singleton的定义如下,这是一个ResourcePool<T>类型的静态原子变量,也就是对每一种不同的数据类型T都有与之对应的变量,也就是说对于每一种数据类型在调用resource pool后会有自己的资源池单例。而这会儿看上面的singleton()函数就是一个很简单的操作:如果已经初始化了直接返回单例,没初始化则新建并返回单例的函数。同时因为是多线程调用所以用pthread_mutex_lock加了锁,并且使用了memory_order_release/consume来保证某线程的新建对其他线程读取的可见性。
在这里插入图片描述
然后我们来看看获取到单例后,调用的ResourcePool单例内部的 get_resource(),有如下几个重载。在这里插入图片描述
这三个get_resource()都是先调用get_or_new_local_pool()获取到一个LocalPool类型的指针lp,随后使用指针调用get,传入id和附加的参数,get_or_new_local_pool()函数如下。首先判断_local_pool是否已经有了,如果有则直接返回,如果没有则新建后返回,新建成功后会调用thread_atexit登记一个pthread退出后删除_local_pool的函数,同时给_nlocal加一,该变量表明T类型的resource pool的local pool数量。
在这里插入图片描述
_local_pool是一个thread local变量,每个pthread都会有一个,注意LocalPool这个类型,它是整个resource pool的资源分配的入口,构造的时候会传入全局的单例resource pool,_local_pool定义声明如下。
在这里插入图片描述
在这里插入图片描述
调用的三个get如下。
在这里插入图片描述
上面的宏定义实现如下,先解释一下注释中提到的POD。
POD全称Plain Old Data是指C风格的struct结构体定义的数据结构,其中struct结构体中只能定义常规数据类型(不能含有自定义数据类型)。它仅作为被动的收藏的字段值,不使用封包或者other object-oriented特征。
对于POD类型T的对象,不管这个对象是否拥有类型T的有效值,如果将该对象的底层字节序列复制到一个字符数组(或者无符号字符数组)中,再将其复制回对象,那么该对象的值与原始值一样。
对于任意的POD类型T,如果两个T指针分别指向两个不同的对象obj1和obj2,如果用memcpy库函数把obj1的值复制到obj2,那么obj2将拥有与obj1相同的值。
简言之,针对POD对象,其二进制内容是可以随便复制的,在任何地方,只要其二进制内容在,就能还原出正确无误的POD对象。对于任何POD对象,都可以使用memset()函数或者其他类似的内存初始化函数。
这里是想要调用new T而不是 new T()来避免不必要的memset,节省开销。
在这里插入图片描述
首先我们先介绍一些其他的定义再看GET的实现。
ResourceId结构体包含一个uint64_t 的value,通过重载uint64_t运算符可以直接当uint64_t类型使用,还有一个模板函数可以实现不同类型的ResourceId转换,ResourceId是resource pool中某个资源的唯一标识identifier,所有的资源获取和归还都是基于ResourceId的,比如获取资源就是resource pool返回资源并将资源id写入到传入的ResourceId里。
在这里插入图片描述
resource pool在内存分配上是按块进行,Block是ResourcePool类里的一个struct,如下。nitem是当前block里已建立的item的数量,items数组本质上则是提前分配好的内存。
在这里插入图片描述
Block则是受BlockGroup管理,nblock表示块的数量,blocks[]存储对应指针。
在这里插入图片描述
ResourcePoolFreeChunk是空闲的Chunk(指用户请求分配的内存),里面有两个变量,空闲的资源个数,和id数组。
在这里插入图片描述
关于它还有两个typedef。FreeChunk是固定大小为FREE_CHUNK_NITEM的一个chunk,而DynamicFreeChunk是利用动态数组实现的一个变长的chunk。
在这里插入图片描述
然后我们就可以回去看GET怎么被实现的了。
首先当本地已有空闲的资源和id,直接从block中找到资源返回。
在这里插入图片描述
_cur_free是一个FreeChunk变量,也就是当前thread的local pool里的空闲资源chunk,nfree是该chunk空闲资源个数,如果大于0,则根据nfree在对应位置取出一个free_id,赋值给传进来的参数id,并且调用unsafe_address_resource去取出资源,unsafe_address_resource就是根据id算出位置,去相应的block group和里面的block取出返回。
在这里插入图片描述
如果本地的chunk没有空闲资源,那么去看有没有全局的free_chunk,_pool->pop_free_chunk(_cur_free)是从全局取一个free_chunk赋值给_cur_free,如果取到了,则进行和上面一样的操作。
在这里插入图片描述
pop_free_chunk函数如下。就是从_free_chunks尾部取一个freechunk拷贝给传进来的freechunk,注意这里拷贝的是空闲的resourceId,根据resourceId可以计算出资源所在块。
在这里插入图片描述
如果进行到这里,说明全局也没有已有的空闲的资源了,这个时候优先考虑从本地block上新建对象, _cur_block 是local pool里的Block类型的类变量。如果_cur_block里已有的item数量小于上限,则直接在里面新建一个对象,id->value指明了新建对象在clock里的位置,address会用到。注意T
* p = new ((T*)_cur_block->items + _cur_block->nitem) T CTOR_ARGS;这个语句,是在不分配内存的情况下指定位置直接新建对象,也就是在((T*)_cur_block->items + _cur_block->nitem)指明的内存空间上新建一个T类型的对象。
在这里插入图片描述
如果依旧不行,则先新建一个block把指针赋给_cur_block,在block里新建对象。add_block函数不再多说。
在这里插入图片描述
接下来再看回收资源,在外部调用的是return_resource函数。对应使用的内部函数在这里。其实是上面分配的反过程。优先返回到本地的_cur_free,如果_cur_free满了,则把_cur_free push到全局的_free_chunks里,然后把当前归还的id放到_cur_free里。
在这里插入图片描述
push基本和上面的pop相反过来。
在这里插入图片描述


总结

以上就是今天介绍的全部内容,本文进行了bthread相关的基础类resource pool部分详细源码分析,之后的博客会继续进行其他代码的分析。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 5G中的SC-FDMA (Single Carrier Frequency Division Multiple Access) 是一种用于无线通信的调制技术,它在5G系统中得到广泛应用。SC-FDMA是OFDMA (Orthogonal Frequency Division Multiple Access) 的一种变种,在传输数据时具有较低的峰值功率,这可以减少电池消耗和辐射。以下是一个简单的SC-FDMA仿真代码示例。 首先,我们需要生成OFDMA的子载波和帧结构。定义子载波数量和每个子载波的带宽,以及符号数和时隙数量。然后计算子载波的频率和抽样周期。根据帧结构,我们可以定义需要传输的数据和调制方式。 接下来,我们需要生成用于传输的信号。首先将数据进行调制,例如将二进制数据映射为星座图。然后将星座图中的点映射到子载波上,并将其余部分填充为零。最后,将每个子载波的信号进行IFFT变换,生成时域信号。 在信道中,我们可以加入各种噪声和干扰。可以在发送端加入高斯噪声、多径衰落模型和其他信道估计误差。接收端需要通过信道估计和等化来恢复传输的数据。 最后,我们可以通过计算误码率来评估SC-FDMA系统的性能。将接收到的信号与原始数据进行比较,计算比特错误率或符号错误率。可以通过调整调制方式、子载波数量和信道参数来优化系统性能。 这只是一个简单的SC-FDMA仿真代码示例,实际的SC-FDMA系统可能会更加复杂。进行SC-FDMA仿真时,还要考虑到其他因素,如同步、干扰抑制和功率控制等。 希望这个简单的解释对你有帮助! ### 回答2: 5G SC-FDMA(Single Carrier Frequency Division Multiple Access)是5G移动通信中一种重要的调制技术。与传统的OFDMA(Orthogonal Frequency Division Multiple Access)相比,SC-FDMA在频域上具有更好的功率效率和频谱利用率。 编写5G SC-FDMA仿真代码可以帮助我们模拟和评估这种调制技术的性能。以下是一个简单的示例代码,用于实现5G SC-FDMA的仿真: 1. 初始化仿真参数: - 设定子载波数量、循环前缀长度、符号数等。 - 定义数据传输速率、载波频率等。 2. 生成随机信号: - 生成要发送的随机数据比特流。 - 将比特流转换为符号序列。 3. SC-FDMA调制: - 将符号序列映射到特定的子载波上。 - 添加循环前缀以避免多径干扰。 4. 信道模型: - 添加信道衰落和噪声。 5. 接收端: - 移除循环前缀。 - 对接收到的信号进行解调。 6. 评估性能: - 计算误码率(BER)或块错误率(BLER)等性能指标。 - 可以与其他调制技术进行比较。 7. 输出结果: - 打印或保存仿真结果,例如误码率曲线、信噪比要求等。 需要注意的是,上述只是一个简化的示例代码框架,具体的实现细节要根据使用的仿真工具和编程语言而定。此外,对于更复杂的场景,可能需要考虑多径效应、天线分集等因素。 总之,编写5G SC-FDMA仿真代码是模拟和评估这种调制技术性能的重要工作,它可以帮助我们更好地理解和优化5G移动通信系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值