news 2026/7/10 7:45:01

线程池模拟实现的后续改进想法

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
线程池模拟实现的后续改进想法

线程池模拟实现的后续改进想法

  • 1问题:原生普通函数不能直接通过AddTask()对函数传参
  • 2:解决方法:
    • 1:成员函数
    • 2:lambda 捕获参数
      • 1:[var] 显式值捕获(拷贝)
      • 2:[&var] 显式引用捕获(别名)
      • 3:混合显式捕获
      • 4:隐式捕获(批量捕获所有变量)
      • 5:隐式 + 单独指定
      • 6:初始化捕获(C++14 通用捕获)
      • 7:特殊捕获规则
    • 3:std::bind 绑定参数
      • 1:bind默认拷贝
      • 2:bind的引用拷贝
      • 3:bind 绑定右值 / 移动语义:std::move
      • 4:占位符 std::placeholders::_1
        • 1.占位符基础含义
        • 2:关键区分:别搞混「原函数形参顺序」和「占位符顺序」
        • 3:搭配类成员函数 bind 常用场景
    • 4:让接口原生支持传参(可变模板重载)
        • 1:拆分语法:
        • 2:`std::forward<Args>(args)`:完美转发
        • 3:包展开语法 `...arg = std::forward<Args>(args)`
        • 4:区分:三种不同位置的 `...`
        • 5:简单总结

1问题:原生普通函数不能直接通过AddTask()对函数传参

template<classFunc>voidAddTask(Func&&func){PushTask(std::forward<Func>(func));}

原理说明:PushTask最终把传入的可调用对象存入queue<TTask>TTask强制是std::function<void()>。不管是普通函数、成员函数,只要原生带参数,都必须外层包一层无参包装器(bind/lambda),把参数固化进去,才能符合队列存储要求。

内部存入队列的类型是TTask = std::function<void()>,要求无参、无返回的可调用对象。

  • 普通函数void test(int a)本身签名是void(int),和void()不匹配,直接传会编译报错;
  • 函数的参数无法通过AddTask接口追加,接口只接收一个可调用对象,没有参数占位。

2:解决方法:

1:成员函数

成员函数专用重载,专门处理类成员 + 对象 + 参数:将this指针bind给普通函数,方便函数后续访问类内成员。

template<classClassType,classFunc>voidAddTask(ClassType*obj,Func&&func){PushTask(std::bind(std::forward<Func>(func),obj));}

2:lambda 捕获参数

[捕获规则] (参数) -> 返回值 { 函数体 };
[]决定 lambda 能访问外部变量,分为值捕获、引用捕获、隐式捕获、this 捕获、初始化捕获五大类。

voidPrintMsg(conststd::string&msg,intnum){std::cout<<msg<<" "<<num<<std::endl;}std::string s="test";intn=123;// lambda捕获变量,封装成无参任务pool.AddTask([s,n](){PrintMsg(s,n);});

lambda 捕获语法详解:

捕获写法含义
[]不捕获任何局部变量
[x]按值捕获变量x
[&x]按引用捕获变量x
[=]按值捕获所有使用到的局部变量
[&]按引用捕获所有使用到的局部变量
[=, &x]默认按值捕获,但x按引用捕获
[&, x]默认按引用捕获,但x按值捕获
[this]捕获当前对象的指针(this
[*this]拷贝整个当前对象(C++17)
[a = expr]初始化捕获(C++14),用expr初始化a

1:[var] 显式值捕获(拷贝)

  • 把外部变量复制一份到 lambda,内部修改不影响外部原变量
  • 捕获的变量默认const,不可修改;要改需加mutable,不是变量本身 const,而是 lambda 默认operator() const,const 成员函数无法修改非 mutable 成员;加 mutable 移除调用运算符的 const 限定,不是修改捕获变量的 const 属性。
inta=10;autof=[a](){a=20;// 报错,const拷贝cout<<a;};f();// mutable 允许修改拷贝副本autof2=[a]()mutable{a=20;// 只改副本,外部a不变cout<<a;};f2();cout<<a;// 输出10

2:[&var] 显式引用捕获(别名)

  • 直接绑定外部变量的地址,内部修改同步影响外部
  • 生命周期风险:若 lambda 生命周期长于外部变量,会悬垂引用
inta=10;autof=[&a](){a=20;};f();cout<<a;// 输出20

3:混合显式捕获

同时捕获值、引用,逗号分隔:

intx=1,y=2;autof=[x,&y](){// x只读,y可修改y=x+10;};

4:隐式捕获(批量捕获所有变量)

[=]全部值捕获,[&]全部引用捕获

inta=1,b=2;autof=[=](){cout<<a+b;};intc=1;autof=[&](){c=100;};

5:隐式 + 单独指定

批量默认捕获,个别变量反向控制:

  • [=, &x]:默认全部值捕获,唯独 x 用引用
  • [&, x]:默认全部引用捕获,唯独 x 用值拷贝
  • 禁止冲突写法:[=, =x][&, &x],重复指定报错。
inta=1,b=2,c=3;// a、c值捕获,b引用捕获autof=[=,&b](){b=a+c;};

6:初始化捕获(C++14 通用捕获)

语法:[变量名 = 表达式]
作用:

  1. 捕获临时变量、右值、移动对象
  2. 重命名外部变量
  3. 捕获无法拷贝只能移动的类型(unique_ptr
inta=10;// 1. 重命名捕获autof1=[b=a](){cout<<b;};// 2. 移动捕获(std::move)autop=std::make_unique<int>(5);autof2=[p=std::move(p)](){cout<<*p;};// 原p已空,所有权转移到lambda内部// 3. 捕获临时表达式autof3=[x=a*2](){cout<<x;};

[&x = ...]引用初始化捕获,绑定表达式左值。
含义:不要求外部有变量 x,直接把右边表达式...的结果,用左值引用绑定到捕获变量x

7:特殊捕获规则

[ ]空捕获:不捕获任何外部变量,能使用 lambda 自身参数、函数内局部变量,静态局部变量。

intg=100;autof=[](){cout<<g;};

3:std::bind 绑定参数

// 普通带参函数voidPrintMsg(conststd::string&msg,intnum){std::cout<<msg<<" "<<num<<std::endl;}// 使用线程池ThreadPool pool;pool.Start();// bind 把参数提前打包成无参functionpool.AddTask(std::bind(PrintMsg,"hello",666));

1:bind默认拷贝

bind 会拷贝 / 移动传入的参数存入内部存储,不是单纯 “拷贝”;传入右值会移动构造副本,传入左值拷贝

voidfunc(int&x){x+=100;}intmain(){inta=10;autotask=std::bind(func,a);// bind内部拷贝了一份a,存的是副本,不是对外部a的引用task();std::cout<<a<<std::endl;// 输出10,外部a没变,副本内部变成110return0;}

2:bind的引用拷贝

想要绑定引用:必须用std::ref()(左值引用)/std::cref()(const 左值引用)

std::ref/std::cref会生成一个引用包装器std::reference_wrapper<T>,bind 识别该类型后,会存储引用而不是拷贝。

voidfunc(int&x){x+=100;}intmain(){inta=10;autotask=std::bind(func,std::ref(a));task();std::cout<<a<<std::endl;// 输出110,外部变量被修改return0;}

3:bind 绑定右值 / 移动语义:std::move

bind可以接收右值,但 bind 内部会永久保存为左值,想要转移资源,需要手动包std::move

voidfunc(std::string s){}voidfunc_rref(std::string&&s){}intmain(){std::string str="hello";// 传入右值临时,bind 允许autob1=std::bind(func,std::string("temp"));// 传入 move 右值,bind 也允许autob2=std::bind(func,std::move(str));return0;}

bind 把所有传入的参数以实体对象存在内部成员里;
当你后续调用b1()b2()时,bind 拿内部存储的对象传给目标函数,这个内部对象是有名字的成员变量 →属于左值

voidfunc_rref(std::string&&s){std::cout<<"右值引用版本\\n";}voidfunc(std::string s){std::cout<<"值拷贝版本\\n";}intmain(){std::string s="test";autob=std::bind(func,std::move(s));b();// 正常运行,但内部左值拷贝,不移动autob=std::bind(func_rref,std::move(s));b();// 编译报错!return0;}
  1. 把右值存进 bind 内部:可以移动构造,没问题;
  2. 调用 bind、把内部参数传给目标函数:bind 无法自动移动 / 转右值,永远传左值;
  3. 不可拷贝类型(unique_ptr)无法使用 bind,直接编译失败,unique_ptr 只允许移动、禁止拷贝;

4:占位符 std::placeholders::_1

bind 预留参数位,后续调用传参时也可以配合 ref:

voidfunc(inta,int&b){b=a;}intmain(){intx=0;autotask=std::bind(func,std::placeholders::_1,std::ref(x));task(666);std::cout<<x;// 666}
1.占位符基础含义

_1_2_3… 都是占位符:

  • _1:调用bind生成的可调用对象时,第一个传入的参数
  • _2:调用时第二个传入参数
  • _n:第 n 个传入参数
voidfunc(inta,intb){cout<<a<<" "<<b;}intmain(){// 把 func 的第一个参数固定为 10,第二个参数用调用时的第1个实参(_1)填充autof=bind(func,10,placeholders::_1);f(20);// 调用时传入20,对应 _1// 输出:10 20}
2:关键区分:别搞混「原函数形参顺序」和「占位符顺序」

占位符只和bind 生成的新函数调用时的参数挂钩,和原函数参数无关。

voidfunc(intx,inty){cout<<x<<","<<y;}intmain(){// _2 是调用 f 时的第2个参数,_1 是第1个autof=bind(func,placeholders::_2,placeholders::_1);f(100,200);// 调用 f 时:_1=100,_2=200// 传给 func:x=_2=200,y=_1=100// 输出:200,100}
3:搭配类成员函数 bind 常用场景

成员函数第一个隐参是this,占位符依然指代调用包装对象时的参数

structTest{voidtest(intv){cout<<v;}};intmain(){Test obj;autof=bind(&Test::test,&obj,placeholders::_1);f(666);// _1 = 666,输出666}

4:让接口原生支持传参(可变模板重载)

voidPrintMsg(conststd::string&msg,intnum){std::cout<<msg<<" "<<num<<std::endl;}template<classFunc,class...Args>voidAddTask(Func&&func,Args&&...args){// 自动绑定参数生成无参任务autotask=[f=std::forward<Func>(func),...arg=std::forward<Args>(args)](){f(arg...);};PushTask(std::move(task));}pool.AddTask(PrintMsg,"abc",789);
1:拆分语法:
template<classFunc,class...Args>voidAddTask(Func&&func,Args&&...args)
  • Args参数包名
  • ...代表可变,可以接收 0/N 个任意类型模板参数;
  • 调用时编译器自动推导所有实参类型打包进Args

Args&&:万能引用(转发引用)
普通T&&是右值引用;
模板参数 + &&组合 =万能引用,能同时绑定左值、右值:

  1. 传入左值变量:Args推导为T&Args&&=T& &&折叠成T&(左值引用)
  2. 传入临时右值:Args推导为TArgs&&=T&&(右值引用)

Args&&... args:参数包 + 函数形参包

  • args是存储所有实参的函数形参包
  • ...写在类型后面:表示把Args里每一种类型都生成一个Args&&形参;
    等价于展开:
voidprint(int,std::string,double);AddTask(print,10,"hello",3.14);// 三个参数时展开voidAddTask(Func&&func,int&&a,string&&b,double&&c)
2:std::forward<Args>(args):完美转发

作用
保留实参原本的左 / 右值属性,不丢失移动语义:

  • 左值传入 → 转发后仍是左值
  • 临时右值传入 → 转发后仍是右值,支持std::move转移资源

原理
std::forward是带条件转换的 static_cast:

template<classT>T&&forward(std::remove_reference_t<T>&t)noexcept{returnstatic_cast<T&&>(t);}
3:包展开语法...arg = std::forward<Args>(args)
autotask=[f=std::forward<Func>(func),...arg=std::forward<Args>(args)](){f(arg...);};
  1. 捕获展开...arg=xxx
    ...arg=std::forward<Args>(args)会对参数包里每一个元素生成捕获项:
    调用AddTask(print,10,"hi",3.14)等价展开:
[f=forward(print),arg0=forward(10),arg1=forward("hi"),arg2=forward(3.14)]

把所有函数参数转发捕获到 lambda 内部,保存一份副本。

  1. 调用展开f(arg...)
    arg...是调用时参数包展开,把捕获的所有变量依次传给函数:
f(arg0,arg1,arg2);
4:区分:三种不同位置的...
  1. 模板参数class... Args:声明可变参数包
  2. 形参Args&&... args:声明可变参数包
  3. 捕获 / 调用...arg/args...:使用时展开包
写法位置位置作用作用
...arg = xxxlambda 捕获[]包名前面批量创建一组捕获变量(定义包)
arg...函数调用()包名后面把已有包拆成多个参数(使用包)
class... Args模板参数声明声明可变参数包
Args&&... args函数形参声明声明可变参数包
voidfunc(std::unique_ptr<int>&&ptr){}template<classFunc,class...Args>voidAddTask(Func&&func,Args&&...args){PushTask(std::bind(std::forward<Func>(func),std::forward<Args>(args)...));}//编译失败:bind内部是左值,无法绑定T&&右值引用template<classFunc,class...Args>voidAddTask(Func&&func,Args&&...args){// 自动绑定参数生成无参任务autotask=[f=std::forward<Func>(func),...arg=std::forward<Args>(args)](){f(arg...);};PushTask(std::move(task));}autotask=[f=std::forward<Func>(func),...arg=std::forward<Args>(args)](){f(arg...);};
5:简单总结
  1. class... Args:定义一组任意数量、任意类型的模板参数包;
  2. Args&&... args:万能引用形参包,同时接收左值 / 右值;
  3. std::forward<Args>(args):完美转发,保留原始左 / 右值属性;
  4. ...arg=xxx/args...:循环展开参数包里所有元素;
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/7/10 7:40:08

Blender 3MF插件:解锁专业3D打印工作流的完整指南

Blender 3MF插件&#xff1a;解锁专业3D打印工作流的完整指南 【免费下载链接】Blender3mfFormat Blender add-on to import/export 3MF files 项目地址: https://gitcode.com/gh_mirrors/bl/Blender3mfFormat 你是否曾为Blender无法直接处理3D打印行业标准格式而烦恼&a…

作者头像 李华
网站建设 2026/7/10 7:38:05

C++17 发布-订阅模式实现:3类线程安全方案对比与性能实测

C17发布-订阅模式实现&#xff1a;3类线程安全方案对比与性能实测当我们需要构建一个高并发的消息中间件时&#xff0c;发布-订阅模式&#xff08;Pub-Sub&#xff09;往往是首选架构。但在实际工程中&#xff0c;单线程的实现远远不能满足需求。本文将深入探讨三种线程安全的C…

作者头像 李华
网站建设 2026/7/10 7:38:08

情感分析工程化:情感词典 + BERT 的混合方案比纯模型稳定

情感分析工程化&#xff1a;情感词典 BERT 的混合方案比纯模型稳定 一、BERT 在 100 条测试中准确率 94%&#xff0c;上线一周掉到 78% 情感分析是 NLP 中看起来最简单的任务——正/负/中性三分类&#xff0c;有什么难的&#xff1f;但真实业务中的情感分析面对的文本&#…

作者头像 李华
网站建设 2026/7/10 7:36:47

中小企业建站平台怎么选?首选条件不是便宜而是能持续维护

企业搜索“中小企业建站平台首选”时&#xff0c;真正想解决的通常不是排名问题&#xff0c;而是网站能不能按时上线、设计是否符合品牌、后台能不能自己维护、后续改图改文案会不会继续产生不可控成本。 中小企业建站平台首选不能只看首年价格&#xff0c;更要看上线周期、后…

作者头像 李华
网站建设 2026/7/10 7:36:35

12位ADC采样精度提升实战:从原理到代码的5个关键误差源分析与校准

12位ADC采样精度提升实战&#xff1a;从原理到代码的5个关键误差源分析与校准在嵌入式系统开发中&#xff0c;ADC采样精度直接影响着测量结果的可靠性。许多工程师在使用STM32等MCU内置12位ADC时&#xff0c;常常发现实际有效位数&#xff08;ENOB&#xff09;远低于理论值&…

作者头像 李华