0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

跨平台的线程池组件--TP组件

Rice嵌入式开发技术分享 2023-04-06 15:39 次阅读

问题产生

  • 无论是LinuxRTOS,还是Android等开发,我们都会用到多线程编程;但是往往很多人在编程时,都很随意的创建/销毁线程的策略来实现多线程编程;很明显这是不合理的做法,线程的创建/销毁代价是很高的。那么我们要怎么去设计多线程编程呢???答案:对于长驻的线程,我们可以创建独立的线程去执行。但是非长驻的线程,我们可以通过线程池的方式来处理这些线程。

线程池概述

  • 线程池,它是一种多线程处理形式,处理过程中将任务添加到队列,然后在创建线程后自动启动这些任务。线程池线程都是后台线程。每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。如果某个线程在托管代码中空闲(如正在等待某个事件),则线程池将插入另一个辅助线程来使所有处理器保持繁忙。如果所有线程池线程都始终保持繁忙,但队列中包含挂起的工作,则线程池将在一段时间后创建另一个辅助线程但线程的数目永远不会超过最大值。超过最大值的线程可以排队,但他们要等到其他线程完成后才启动。

  • 在一个系统中,线程数过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。线程数过多会导致额外的线程切换开销。

  • 线程的创建-销毁对系统性能影响很大:

  1. 创建太多线程,将会浪费一定的资源,有些线程未被充分使用。
  2. 销毁太多线程,将导致之后浪费时间再次创建它们。
  3. 创建线程太慢,将会导致长时间的等待,性能变差。
  4. 销毁线程太慢,导致其它线程资源饥饿
  • 线程池的应用场景:

  1. 单位时间内处理的任务频繁,且任务时间较短;
  2. 对实时性要求较高。如果接收到任务之后再创建线程,可能无法满足实时性的要求,此时必须使用线程池;
  3. 必须经常面对高突发性事件。比如 Web 服务器。如果有足球转播,则服务器将产生巨大冲击,此时使用传统方法,则必须不停的大量创建、销毁线程。此时采用动态线程池可以避免这种情况的发生。
  • 线程池的应用例子:

  1. EventBus:它是Android的一个事件发布/订阅轻量级框架。其中事件的异步发布就采用了线程池机制。
  2. Samgr:它是OpenHarmony的一个服务管理组件,解决多服务的管理的策略,减低了线程的创建开销。
  • 作者最近在开发的过程中,也遇到多线程编程问题,跨平台,并发任务多,执行周期短。如果按照以往的反复的创建/销毁线程,显然不是一个很好的软件设计。我们需要利用线程池的方式来解决我们问题。

TP(Thread Pool)组件

TP组件,又称线程池组件。是作者编写一个多线程管理组件,特点:

  1. 跨平台:它支持任意的RTOS系统,Linux系统。
  2. 易移植:该组件默认支持CMSIS和POSIX接口,其他RTOS可以轻易适配兼容。
  3. 接口简单:用户操作接口简单,只有三个接口:创建线程池,增加task到线程池,销毁线程池。

TP原理

a246a43e-cd3b-11ed-a826-dac502259ad0.png
  • ① 创建一个线程池,线程池中维护一个Task队列,用于Task任务;理论上:线程池中线程数目至少一个,最多无数个,但是我们要系统能力决定。
  • ② 应用层根据业务需求,创建对应Task,Task数目不限制,根据系统资源创建。
  • ③ 应用层创建的Task,会被挂在Task队列中。
  • ④ 线程池的空闲线程,会检测Task队列中是否为空,如果Task队列不为空,则提取一个Task在线程中执行。

TP实现

适配层实现

为了实现跨平台,需要将差异性接口抽象出来,我们整个组件需要抽象几个内容:①日志接口;②内存管理接口;③ 线程接口;④互斥量接口;⑤信号量接口。以CMSIS接口为例的实现:

  1. 错误码:提供了四种错误码:无错误,错误,内存不足,无效参数
typedefenum{
TP_EOK=0,//Thereisnoerror
TP_ERROR,//Agenericerrorhappens
TP_ENOMEM,//Nomemory
TP_EINVAL,//Invalidargument
}TpErrCode;
  1. 日志接口适配:
  • 需修改宏定义:TP_PRINT;
  • 支持三个等级日志打印:错误信息日志,运行信息日志,调试信息日志的打印。并且支持带颜色。
#defineTP_PRINTprintf

#defineTP_LOGE(...)TP_PRINT("33[31;22m[E/TP](%s:%d)",__FUNCTION__,__LINE__);
TP_PRINT(__VA_ARGS__);
TP_PRINT("33[0mn")
#defineTP_LOGI(...)TP_PRINT("33[32;22m[I/TP](%s:%d)",__FUNCTION__,__LINE__);
TP_PRINT(__VA_ARGS__);
TP_PRINT("33[0mn")
#defineTP_LOGD(...)TP_PRINT("[D/TP](%s:%d)",__FUNCTION__,__LINE__);
TP_PRINT(__VA_ARGS__);
TP_PRINT("n")
  1. 内存接口:只需适配申请内存和释放内存宏定义
#defineTP_MALLOCmalloc
#defineTP_FREEfree
  1. 线程接口:
//tp_def.h
typedefvoid*TpThreadId;

typedefvoid*(*tpThreadFunc)(void*argv);

typedefstruct{
char*name;
uint32_tstackSize;
uint32_tpriority:8;
uint32_treserver:24;
}TpThreadAttr;

TpThreadIdTpThreadCreate(tpThreadFuncfunc,void*argv,constTpThreadAttr*attr);
voidTpThreadDelete(TpThreadIdthread);
  • 创建线程: TpThreadId TpThreadCreate(tpThreadFunc func, void *argv, const TpThreadAttr *attr);
「参数」 「说明」
func 线程入口函数
argv 线程入口函数参数
attr 线程属性:线程名,栈空间,优先级
「返回」 --
NULL 创建失败
线程句柄 创建成功
  • 删除线程:void TpThreadDelete(TpThreadId thread);
「参数」 「说明」
thread 线程句柄
  • CMSIS适配:
//tp_threa_adapter.c
#include"tp_def.h"
#include"cmsis_os2.h"

TpThreadIdTpThreadCreate(tpThreadFuncfunc,void*argv,constTpThreadAttr*attr)
{
osThreadId_tthread=NULL;
osThreadAttr_ttaskAttr={
.name=attr->name,
.attr_bits=0,
.cb_mem=NULL,
.cb_size=0,
.stack_mem=NULL,
.stack_size=attr->stackSize,
.priority=(osPriority_t)attr->priority,
.tz_module=0,
.reserved=0,
};

thread=osThreadNew((osThreadFunc_t)func,argv,&taskAttr);
return(TpThreadId)thread;
}

voidTpThreadDelete(TpThreadIdthread)
{
if(thread!=NULL){
osThreadTerminate(thread);
}
}
  1. 互斥量接口:
//tp_def.h
typedefvoid*TpMutexId;

TpMutexIdTpMutexCreate(void);
TpErrCodeTpMutexLock(TpMutexIdmutex);
TpErrCodeTpMutexUnlock(TpMutexIdmutex);
voidTpMutexDelete(TpMutexIdmutex);
  • 创建互斥量:TpMutexId TpMutexCreate(void);
「参数」 「说明」
「返回」 --
NULL 创建失败
互斥量句柄 创建成功
  • 获取互斥量:TpErrCode TpMutexLock(TpMutexId mutex);
「参数」 「说明」
mutex 互斥量句柄
「返回」 --
TP_EINVAL mutex无效参数
TP_ERROR 获取互斥量失败
TP_EOK 成功获取互斥量
  • 释放互斥量:TpErrCode TpMutexUnlock(TpMutexId mutex);
「参数」 「说明」
mutex 互斥量句柄
「返回」 --
TP_EINVAL mutex无效参数
TP_ERROR 释放互斥量失败
TP_EOK 成功释放互斥量
  • 删除互斥量:void TpMutexDelete(TpMutexId mutex);
「参数」 「说明」
mutex 互斥量句柄
  • CMSIS适配:
//tp_mutex_adapter.c
#include"tp_def.h"
#include"cmsis_os2.h"

TpMutexIdTpMutexCreate(void)
{
osMutexId_tmutex=NULL;
mutex=osMutexNew(NULL);

return(TpMutexId)mutex;
}

TpErrCodeTpMutexLock(TpMutexIdmutex)
{
if(mutex==NULL){
returnTP_EINVAL;
}
if(osMutexAcquire((osMutexId_t)mutex,osWaitForever)==osOK){
returnTP_EOK;
}
returnTP_ERROR;
}

TpErrCodeTpMutexUnlock(TpMutexIdmutex)
{
if(mutex==NULL){
returnTP_EINVAL;
}
if(osMutexRelease((osMutexId_t)mutex)==osOK){
returnTP_EOK;
}
returnTP_ERROR;
}

voidTpMutexDelete(TpMutexIdmutex)
{
if(mutex==NULL){
return;
}
osMutexDelete(mutex);
}
  1. 信号量接口:
//tp_def.h
typedefvoid*TpSemId;

TpSemIdTpSemCreate(uint32_tvalue);
TpErrCodeTpSemAcquire(TpSemIdsem);
TpErrCodeTpSemRelease(TpSemIdsem);
voidTpSemDelete(TpSemIdsem);
  • 创建信号量:TpSemId TpSemCreate(uint32_t value);
「参数」 「说明」
「返回」 --
NULL 创建失败
信号量句柄 创建成功
  • 获取信号量:TpErrCode TpSemAcquire(TpSemId sem);
「参数」 「说明」
sem 信号量句柄
「返回」 --
TP_EINVAL sem无效参数
TP_ERROR 获取信号量失败
TP_EOK 成功获取信号量
  • 释放信号量:TpErrCode TpSemRelease(TpSemId sem);
「参数」 「说明」
sem 信号量句柄
「返回」 --
TP_EINVAL 信号量无效参数
TP_ERROR 释放信号量失败
TP_EOK 成功释放信号量
  • 删除信号量:void TpSemDelete(TpSemId sem);
「参数」 「说明」
sem 信号量句柄
  • CMSIS适配:
//tp_sem_adapter.c
#include"tp_def.h"
#include"cmsis_os2.h"

TpSemIdTpSemCreate(uint32_tvalue)
{
osSemaphoreId_tsem=NULL;
sem=osSemaphoreNew(1,value,NULL);

return(TpSemId)sem;
}

TpErrCodeTpSemAcquire(TpSemIdsem)
{
if(sem==NULL){
returnTP_EINVAL;
}
if(osSemaphoreAcquire((osSemaphoreId_t)sem,osWaitForever)!=osOK){
returnTP_ERROR;
}
returnTP_EOK;
}

TpErrCodeTpSemRelease(TpSemIdsem)
{
if(sem==NULL){
returnTP_EINVAL;
}
if(osSemaphoreRelease((osSemaphoreId_t)sem)!=osOK){
returnTP_ERROR;
}
returnTP_EOK;
}

voidTpSemDelete(TpSemIdsem)
{
if(sem==NULL){
return;
}
osSemaphoreDelete((osSemaphoreId_t)sem);
}

核心层实现

tp的提供的接口非常精简:创建线程池,增加任务到线程池,销毁线程池。

  1. 创建线程池:
  • 接口描述:TpErrCode TpCreate(Tp *pool, const char *name, uint32_t stackSize, uint8_t threadNum);
「参数」 「说明」
pool 线程池句柄
name 线程池中线程名字
stackSize 线程池中线程的栈大小
theadNum 线程池中线程数目
「返回」 --
TP_EINVAL pool无效参数
TP_ERROR 创建失败
TP_NOMEM 内存不足
TP_EOK 创建成功
  • 接口实现:
    • ①创建task队列增删互斥量:管理task队列的增加及释放的互斥关系,保证增加和释放为同步策略。
    • ②创建task队列状态信号量:当task队列非空则释放信号量,线程池中的线程可以从task队列中获取task执行。
    • ③创建线程池中线程:根据threadNum参数,创建对应的线程数目。
TpErrCodeTpCreate(Tp*pool,constchar*name,
uint32_tstackSize,uint8_tthreadNum)
{
intindex=0;
if(pool==NULL){
TP_LOGE("ThreadpoolhandleisNULL");
returnTP_EINVAL;
}
//①
if((pool->queueLock=TpMutexCreate())==NULL){
TP_LOGE("Createthreadpoolmutexfailed");
returnTP_ERROR;
}
//②
if((pool->queueReady=TpSemCreate(0))==NULL){
TP_LOGE("Createthreadpoolsemfailed");
returnTP_ERROR;
}
pool->taskQueue=NULL;
pool->threadNum=threadNum;
pool->waitTaskNum=0;
pool->threads=(TpThreadInfo*)TP_MALLOC(threadNum*sizeof(TpThreadInfo));
if(pool->threads==NULL){
TP_LOGE("Mallocthreadpoolinfomemoryfailed");
returnTP_ENOMEM;
}
//③
for(index=0;index< threadNum; index++) {
        pool->threads[index].attr.name=(char*)TP_MALLOC(TP_THREAD_NAME_LEN);
if(pool->threads[index].attr.name==NULL){
TP_LOGE("Mallocthreadnamememoryfailed");
returnTP_ENOMEM;
}
snprintf(pool->threads[index].attr.name,TP_THREAD_NAME_LEN,"%s%d",name,index);
pool->threads[index].attr.stackSize=stackSize;
pool->threads[index].attr.priority=TP_THREAD_PRIORITY;
pool->threads[index].threadId=TpThreadCreate(TpThreadHandler,pool,&pool->threads[index].attr);

}
returnTP_EOK;
}
  1. 增加任务到线程池:
  • 接口描述:TpErrCode TpAddTask(Tp *pool, taskHandle handle, void *argv);
「参数」 「说明」
pool 线程池句柄
handle 线程池中线程名字
argv 线程池中线程的栈大小
「返回」 --
TP_EINVAL pool无效参数
TP_NOMEM 内存不足
TP_EOK 增加task成功
  • 接口实现:
    • ① 创建一个task句柄,并将注册task函数和函数的入参。
    • ② 获取task队列互斥量,避免增加队列成员时,在释放队列成员。
    • ③ 释放task信号量,通知线程池中的线程可以从task队列中获取task执行
TpErrCodeTpAddTask(Tp*pool,taskHandlehandle,void*argv)
{
TpTask*newTask=NULL;
TpTask*taskLIst=NULL;

if(pool==NULL){
TP_LOGE("ThreadpoolhandleisNULL");
returnTP_EINVAL;
}
//①
newTask=(TpTask*)TP_MALLOC(sizeof(TpTask));
if(newTask==NULL){
TP_LOGE("Mallocnewtaskhandlememoryfailed");
returnTP_ENOMEM;
}
newTask->handle=handle;
newTask->argv=argv;
newTask->next=NULL;
//②
TpMutexLock(pool->queueLock);
taskLIst=pool->taskQueue;
if(taskLIst==NULL){
pool->taskQueue=newTask;
}
else{
while(taskLIst->next!=NULL){
taskLIst=taskLIst->next;
}
taskLIst->next=newTask;
}
pool->waitTaskNum++;
TpMutexUnlock(pool->queueLock);
//③
TpSemRelease(pool->queueReady);
returnTP_EOK;
}
  1. 销毁线程池
  • 接口描述:TpErrCode TpDestroy(Tp *pool);
「参数」 「说明」
pool 线程池句柄
「返回」 --
TP_EINVAL pool无效参数
TP_EOK 销毁成功
  • 接口实现:
    • ① 删除线程池中所有线程。
    • ② 删除task队列互斥量,task状态信号量。
    • ③ 删除线程池的Task队列。
TpErrCodeTpDestroy(Tp*pool)
{
intindex=0;
TpTask*head=NULL;

if(pool==NULL){
TP_LOGE("ThreadpoolhandleisNULL");
returnTP_EINVAL;
}
//①
for(index=0;index< pool->threadNum;index++){
TpThreadDelete(pool->threads[index].threadId);
pool->threads[index].threadId=NULL;
TP_FREE(pool->threads[index].attr.name);
pool->threads[index].attr.name=NULL;
}
//②
TpMutexDelete(pool->queueLock);
pool->queueLock=NULL;
TpSemDelete(pool->queueReady);
pool->queueReady=NULL;

TP_FREE(pool->threads);
pool->threads=NULL;
//③
while(pool->taskQueue!=NULL){
head=pool->taskQueue;
pool->taskQueue=pool->taskQueue->next;
TP_FREE(head);
}
pool=NULL;
returnTP_EOK;
}
  1. 线程池中线程函数
  • 接口描述:static void *TpThreadHandler(void *argv)
「参数」 「说明」
argv 线程池参数
  • 接口实现:
    • ① 获取task队列互斥量,避免增加队列成员时,在释放队列成员。
    • ② 当task队列为空时,将阻塞在获取信号量,等待用户增加task时释放信号量。
    • ③ 当task队列不为空,则从task队列中获取task,并执行。
    • ④ 当task执行完,会将对应的task句柄删除。
staticvoid*TpThreadHandler(void*argv)
{
Tp*pool=(Tp*)argv;
TpTask*task=NULL;

while(1){
//①
TpMutexLock(pool->queueLock);
//②
while(pool->waitTaskNum==0){
TpMutexUnlock(pool->queueLock);
TpSemAcquire(pool->queueReady);
TpMutexLock(pool->queueLock);
}
//③
task=pool->taskQueue;
pool->waitTaskNum--;
pool->taskQueue=task->next;
TpMutexUnlock(pool->queueLock);
task->handle(task->argv);
//④
TP_FREE(task);
task=NULL;
}
}

TP应用

  1. 测试例程:
  • 创建一个线程池,线程池中包含3个线程,线程的名字为tp,栈为1024byte。
  • 在线程池中创建6个task,其中,task参数为taskId。
#include"tp_manage.h"

Tppool;
voidTestTaskHandle(void*argv)
{
printf("%s--taskId:%drn",__FUNCTION__,(uint32_t)argv);
}

intmain(void)
{
//①
TpCreate(&pool,"tp",1024,3);
//②
TpAddTask(&pool,TestTaskHandle,(void*)1);
TpAddTask(&pool,TestTaskHandle,(void*)2);
TpAddTask(&pool,TestTaskHandle,(void*)3);
TpAddTask(&pool,TestTaskHandle,(void*)4);
TpAddTask(&pool,TestTaskHandle,(void*)5);
TpAddTask(&pool,TestTaskHandle,(void*)6);

return0;
}
  1. RTOS中的CMSIS运行效果:
a26a17ca-cd3b-11ed-a826-dac502259ad0.png
  1. Linux中POSIX接口运行效果:
a29bd634-cd3b-11ed-a826-dac502259ad0.png

总结

  1. 线程池是多线程的一个编程方式,它避免了线程的创建和销毁的开销,提高了系统的性能。
  2. 增加到线程池中的任务是非长驻的,不能存在死循环,否则她会一直持有线程池中的某一个线程。
  3. TP线程池组件的开发仓库链接:

    [TP组件](https://gitee.com/RiceChen0/tp.git)

    欢迎关注微信公众号『Rice嵌入式开发技术分享』


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • Linux
    +关注

    关注

    87

    文章

    10990

    浏览量

    206736
  • RTOS
    +关注

    关注

    20

    文章

    776

    浏览量

    118796
  • 组件
    +关注

    关注

    1

    文章

    336

    浏览量

    17584
  • 线程
    +关注

    关注

    0

    文章

    489

    浏览量

    19495
收藏 人收藏

    评论

    相关推荐

    基于Acegi技术的通用权限管理组件的研究

    了一个基于Java平台的通用的权限管理组件。该组件面向开发人员,具有良好的移植性、扩展性和通用性,程序的开发者可以在该组件的基础上开发出高效的,便捷的权限管理系统。本文最后通过将该
    发表于 04-24 09:21

    户外光伏组件户外测试平台设计

      在传统工作模式电子负载的基础上提出的一种户外光伏组件测试平台,以自动切换工作模式的可编程电子负载为核心,实现了对光伏组件IV特性曲线更加精确而完整地测量。它可根据用户设定,使光伏组件
    发表于 09-30 16:16

    修改的emfile组件能作为标准组件吗?

    最近,用户使用EMFLASH组件并行地与另一个SPI设备存在问题。由于MOSI和MISO线路没有被显示,这似乎是不可能的。看看这个线程。塞浦路斯通过提供一个改变了的SPI信号的电子文件组件来帮助他
    发表于 07-18 11:35

    平头哥剑CDK 更新重磅来袭!三大亮点速看!

    效的嵌入式程序而提供的工具; 与传统的嵌入式集成开发环境不同的是,剑CDK内部自动对接芯片开放平台,自动获取芯片开放平台上的开发资源。在芯片开放平台上,包含了一个网络
    发表于 09-01 15:00

    HarmonyOS应用开发-三方UI线程组件练习

    ://jitpack.io" }...... }此处引用的是一个UI线程组件,可以在页面布局代码中直接使用,如下: 效果如下: 完整代码地址:https://gitee.com/jltfcloudcn
    发表于 10-23 11:13

    HarmonyOS组件更新,新增700+开源组件

    开发者可以直接通过HarmonyOS Gitee开源地址下载源码或从HarmonyOS应用包管理平台获取相关组件。1.HarmonyOS Gitee开源地址如图2所示,点击下方链接,进入
    发表于 11-18 11:17

    线程是如何实现的

    线程的概念是什么?线程是如何实现的?
    发表于 02-28 06:20

    YoC组件发布开源操作指南须知

    个解决方案由芯片SDK虚拟组件、子系统以及第三方SDK组成。2. 组件开发开发者可以利用剑CDK和yoctools进行组件开发,通过下列方式进行安装剑
    发表于 03-09 07:37

    线程创建的两种方法

    1. 使用内置模块在使用多线程处理任务时也不是线程越多越好,由于在切换线程的时候,需要切换上下文环境,依然会造成cpu的大量开销。为解决这个问题,线程
    发表于 03-16 16:15

    HarmonyOS任意组件实现通信的方式

    与child1/child2是父子组件关系、child1与child2是兄弟组件关系、parent与child1/child2是层级组件的关系,详细的代码文件可以参考附件源码,最终预
    发表于 03-25 15:55

    基于组件的继电保护测试软件平台的研究

    介绍了组件技术的优点,并将组件技术和软件平台的设计思想应用于继电保护测试软件的设计中,提出了基于组件技术的继电保护测试软件平台的构架方案的设
    发表于 01-18 11:58 14次下载

    电缆组件是什么,电缆组件的应用有哪些

    电缆组件是用来连接不同的电子设备系统或者分系统之间的电气连接组件,是由各种绝缘电线、屏蔽线和电连接器组成。随着电缆组件在通讯领域的广泛应用,对电缆组件电气性能的稳定性,使用寿命及耐环境
    发表于 06-22 17:09 4142次阅读

    电缆组件是什么?电缆组件的应用-科兰

    随着电子科技的发展,电缆配件在电子通信行业的应用也是越来越广泛,影响着我们工作生活的方方面面。今天科兰小编就和大家一起来学习学习电缆组件是什么,电缆组件的应用等知识。 电缆组件是什么? 电缆
    的头像 发表于 05-19 11:18 868次阅读

    可重用的验证组件中构建测试平台的步骤

    本文介绍了从一组可重用的验证组件中构建测试平台所需的步骤。UVM促进了重用,加速了测试平台构建的过程。 首先对 测试平台集成者(testbench integrator) 和 测试编写
    的头像 发表于 06-13 09:14 354次阅读
    可重用的验证<b class='flag-5'>组件</b>中构建测试<b class='flag-5'>平台</b>的步骤

    深入测量光伏组件组件功率

    随着光伏产业的发展,光伏组件器件的性能以及其可靠性受到了广泛关注和深度重视。光伏组件组件功率测试是评估光伏组件性能是否符合产业化标准的重要手段,它能够准确测量光伏
    的头像 发表于 10-10 10:16 852次阅读
    深入测量光伏<b class='flag-5'>组件</b>的<b class='flag-5'>组件</b>功率