自定义线程池1.2
自定义线程池 1.2
1. 简介
上次我们实现了 1.1 版本,将线程池中的线程数量交给使用者决定,并且将线程的创建延迟到任务提交的时候,在本文中我们将对这个版本进行如下的优化:
- 在新建线程时交给线程一个任务。
- 让线程在某种情况下停止。
2. 优化一
在 1.1 版本的实现中,我们使用阻塞队列作为媒介,将 提交任务 和 执行任务 变成 异步 的,和 消息队列 类似,将消息的 生产 和 消费 变成 异步 的,俗称 解耦。
好处是 降低了系统的耦合性,增强了系统的扩展性。对于消息队列而言,扩展性在于支持 同一消费组中的多个消费者同时订阅同一个主题,进行 动态地水平扩展,从而增加整个消费组消费消息的性能;对于线程池而言,扩展性在于支持 多个线程同时“订阅”同一个阻塞队列,在运行过程中增加线程池处理任务的能力。
但随之而来的缺点就是 存在一定的延迟,这是异步一定会产生的问题。对于同步而言,提交任务和执行任务是直接的方法调用:submit -> execute
;而对于异步而言,提交任务和执行任务通过阻塞队列解耦,流程会变成这样:submit -> taskQueue, taskQueue -> execute
,从 submit
到 execute
会有一定的延迟。
注:当阻塞队列为空时,从
offer()
到take()
是有一定延迟的。因为在阻塞队列的实现中,例如ArrayBlockingQueue
,offer()
和take()
会共用一个条件变量notEmpty
:
- 当
take()
时,如果发现队列中没有任务,则会调用notEmpty.await()
阻塞当前线程。- 当
offer()
时,会调用notEmpty.signal()
来唤醒所有使用notEmpty.await()
阻塞的线程。新建一个线程后,这个线程就调用
take()
阻塞起来,之后执行到offer()
往队列中增加一个任务后,才唤醒了这个线程,延迟就存在于 阻塞到唤醒 的过程。
对于线程池来说,为了在运行时动态增加线程的数量,异步是必需的,但是当提交新任务时,希望尽量能减少提交到执行的延迟,应该怎么做呢?
揭晓答案:给线程一个初始任务 initialTask
,线程一旦启动就去执行这个初始任务,然后再进入无限循环,从阻塞队列中领取任务。
此外,我们需要注意到一个细节:如果我们将初始任务作为线程的一个成员字段 initialTask
,在构造方法中传入,在 run()
方法中执行,那么在 run()
方法中,当初始任务执行完之后,这个初始任务就再也不会用到了,如果 一直保留对其的引用,则会造成 内存泄漏,所以我们应该 在执行完初始任务后将 initialTask
置为空。
于是我们得到了如下的任务类:
/*** 线程池中的线程执行的任务,永不结束*/
private final class Worker implements Runnable {/*** 线程执行的初始任务*/private Runnable initialTask;public Worker(Runnable initialTask) {this.initialTask = initialTask;}@Overridepublic void run() {initialTask.run();initialTask = null; // help GCwhile (true) {Runnable t = null;try {// 取出任务,这步操作是阻塞的,如果线程没有取到任务,则会一直阻塞在这里t = taskQueue.take();} catch (InterruptedException e) {throw new RuntimeException(e);}t.run();}}
}
3. 优化二
上面提到过,在提交任务时,调用 offer()
会唤醒 所有 阻塞在 take()
中的线程。如果有很多线程都在阻塞,当提交任务时,这些线程都会被唤醒,去争抢同一个任务,但只有一个线程能够抢到,而其他线程被唤醒后再次在 take()
中阻塞。假如这些线程的数量十分庞大,那么将会消耗很多性能。
这种情况也算做 线程饥饿,不过线程饥饿真正的定义是 在循环中长时间抢不到锁,线程一直处于 运行 状态,而线程池中的线程大部分时间处于在
take()
中的阻塞状态,只有提交任务才会唤醒它们。
所以想到 线程长时间领不到任务就停止运行,于是得到了如下的任务类:
/*** 线程池中的线程执行的任务,在指定时间内无法获取到任务时结束*/
private final class Worker implements Runnable {/*** 线程执行的初始任务*/private Runnable initialTask;public Worker(Runnable initialTask) {this.initialTask = initialTask;}/*** 对在 {@link #threadPool} 中的线程的引用,用于在线程退出时将线程从 {@link #threadPool} 中移除*/private Thread threadInPool;public void setThreadInPool(Thread threadInPool) {this.threadInPool = threadInPool;}@Overridepublic void run() {initialTask.run();initialTask = null; // help GCwhile (true) {Runnable t = null;try {// 取出任务,这步操作是阻塞的,如果线程没有取到任务,则会在这里阻塞 3st = taskQueue.poll(3, TimeUnit.SECONDS);} catch (InterruptedException e) {throw new RuntimeException(e);}// 检查是否获取到任务了,如果没有则退出循环,停止运行if (t == null) {break;}t.run();}if (threadInPool == null) {throw new RuntimeException("没有设置线程");}// 从线程池中移除当前线程synchronized (threadPoolMonitor) {threadPool.remove(threadInPool);}}
}
4. 实现 1.2 版本
public class ThreadPool1_2 {/*** 线程池中线程的数量*/private final int poolSize;/*** 存放线程的集合,使用 {@link Set} 是因为 {@link Set#remove(Object)} 性能更高*/private final Set<Thread> threadPool = new HashSet<>();/*** 线程池的管程* <p>* 用于保证 <strong>获取线程池大小</strong>、<strong>将线程放入线程池</strong>、<strong>从线程池中移除线程</strong> 的互斥性*/private final Object threadPoolMonitor = new Object();/*** 任务队列,大小为 10*/private final BlockingQueue<Runnable> taskQueue = new ArrayBlockingQueue<>(10);/*** 线程池中的线程执行的任务,在指定时间内无法获取到任务时结束*/private final class Worker implements Runnable {/*** 线程执行的初始任务*/private Runnable initialTask;public Worker(Runnable initialTask) {this.initialTask = initialTask;}/*** 对在 {@link #threadPool} 中的线程的引用,用于在线程退出时将线程从 {@link #threadPool} 中移除*/private Thread threadInPool;public void setThreadInPool(Thread threadInPool) {this.threadInPool = threadInPool;}@Overridepublic void run() {initialTask.run();initialTask = null; // help GCwhile (true) {Runnable t = null;try {// 取出任务,这步操作是阻塞的,如果线程没有取到任务,则会在这里阻塞 3st = taskQueue.poll(3, TimeUnit.SECONDS);} catch (InterruptedException e) {throw new RuntimeException(e);}// 检查是否获取到任务了,如果没有则退出循环,停止运行if (t == null) {break;}t.run();}if (threadInPool == null) {throw new RuntimeException("没有设置线程");}// 从线程池中移除当前线程synchronized (threadPoolMonitor) {threadPool.remove(threadInPool);}}}/*** 构造一个线程池** @param poolSize 线程池中的线程数量*/public ThreadPool1_2(int poolSize) {this.poolSize = poolSize;}/*** 提交任务** @param task 待执行的任务*/public void submit(Runnable task) {synchronized (threadPoolMonitor) {// 如果线程数量小于最大线程数量,则新建一个线程执行任务,然后直接返回if (threadPool.size() < poolSize) {Worker taskRunInThread = new Worker(task);Thread thread = new Thread(taskRunInThread);taskRunInThread.setThreadInPool(thread);threadPool.add(thread);thread.start();return;}}// 否则将任务放到任务队列中,等待空闲线程执行taskQueue.offer(task);}/*** 获取当前线程池中的线程数量** @return 当前线程池中的线程数量*/public int getCurrPoolSize() {synchronized (threadPoolMonitor) {return threadPool.size();}}
}
注意:本实现中将
threadPool
的类型从ArrayList
变成了HashSet
,因为Set.remove()
的性能更高。
5. 测试程序
public class ThreadPool1_2Test {/*** 测试线程池 1.2 版本的基本功能*/@Testpublic void test() throws InterruptedException {final int taskSize = 10;CountDownLatch latch = new CountDownLatch(taskSize);ThreadPool1_2 threadPool = new ThreadPool1_2(5);LogUtil.infoWithTimeAndThreadName("提交任务前");for (int i = 0; i < taskSize; i++) {int finalI = i;threadPool.submit(() -> {try {Thread.sleep(1000);} catch (InterruptedException e) {throw new RuntimeException(e);}LogUtil.infoWithTimeAndThreadName("正在执行任务" + finalI);latch.countDown();});}LogUtil.infoWithTimeAndThreadName("提交任务后");// 等待测试结束latch.await();LogUtil.infoWithTimeAndThreadName("任务执行完毕");}/*** 测试线程池 1.2 版本的 线程 3s 领不到任务就停止 的功能*/@Testpublic void testBreak() throws InterruptedException {final int taskSize = 10;CountDownLatch latch = new CountDownLatch(taskSize);ThreadPool1_2 threadPool = new ThreadPool1_2(5);LogUtil.infoWithTimeAndThreadName("提交任务前");for (int i = 0; i < taskSize; i++) {threadPool.submit(() -> latch.countDown());}LogUtil.infoWithTimeAndThreadName("提交任务后");// 等待测试结束latch.await();LogUtil.infoWithTimeAndThreadName("任务执行完毕");// 等待 3.1s,所有线程都停止,多出来的 100ms 是为了避免延迟导致线程停止较晚Thread.sleep(3100);LogUtil.infoWithTimeAndThreadName("当前线程数量为:" + threadPool.getCurrPoolSize());}
}
6. 测试结果
6.1 test
00:19:31 [ main] 提交任务前
00:19:31 [ main] 提交任务后
00:19:32 [Thread-3] 正在执行任务3
00:19:32 [Thread-1] 正在执行任务1
00:19:32 [Thread-2] 正在执行任务2
00:19:32 [Thread-0] 正在执行任务0
00:19:32 [Thread-4] 正在执行任务4
00:19:33 [Thread-0] 正在执行任务8
00:19:33 [Thread-1] 正在执行任务6
00:19:33 [Thread-3] 正在执行任务5
00:19:33 [Thread-2] 正在执行任务7
00:19:33 [Thread-4] 正在执行任务9
00:19:33 [ main] 任务执行完毕
注:对于
test()
方法测试的结果解析,可以参考 1.0 版本。
6.2 testBreak
00:21:06 [ main] 提交任务前
00:21:06 [ main] 提交任务后
00:21:06 [ main] 任务执行完毕
00:21:09 [ main] 当前线程数量为:0
对于这个方法的测试结果,我们需要看到最后输出的 “当前线程数量为:0”,因为只要线程在 3s 内没有领到任务,就会停止,从而到达线程池中线程数量为 0
的效果。
7. 思考
- 在本实现中,
threadPoolMonitor
的使用变得更频繁了,你能想出每次使用threadPoolMonitor
加锁的原因是什么吗? - 之前讲到“池化”思想是 提前创建/延迟删除,在 1.2 版本的
testBreak
测试中,我们发现最终线程池中没有一个线程,这与“池化”的思想有悖。你能调整一下设计,使新的设计 在一定程度上 满足“池化”思想吗?(这里如果有很大疑惑,说明我之前讲的池化思想太简略了,下次实现新版本会补充)
8. 总结
本次我们实现了 1.2 版本,优化一基于 异步必定有延迟 的思想,优化二基于 减少线程无效唤醒 的思想。
9. 补充
当我在实现更高版本时,我发现当前版本还存在另一种设计,如下所示:
public class ThreadPool1_2 {/*** 线程池中线程的数量*/private final int poolSize;/*** 存放线程的集合,使用 {@link Set} 是因为 {@link Set#remove(Object)} 性能更高*/private final Set<Worker> threadPool = new HashSet<>();/*** 线程池的管程* <p>* 用于保证 <strong>获取线程池大小</strong>、<strong>将线程放入线程池</strong>、<strong>从线程池中移除线程</strong> 的互斥性*/private final Object threadPoolMonitor = new Object();/*** 任务队列,大小为 10*/private final BlockingQueue<Runnable> taskQueue = new ArrayBlockingQueue<>(10);/*** 线程池中的线程执行的任务,在指定时间内无法获取到任务时结束*/private final class Worker implements Runnable {/*** 线程执行的初始任务*/private Runnable initialTask;/*** 对 <strong>真正运行的线程</strong> 的引用,用于调用其 {@link Thread#start()} 方法启动线程*/private final Thread actuallyRunningThread;public Worker(Runnable initialTask) {this.initialTask = initialTask;this.actuallyRunningThread = new Thread(this);threadPool.add(this);}@Overridepublic void run() {initialTask.run();initialTask = null; // help GCwhile (true) {Runnable t = null;try {// 取出任务,这步操作是阻塞的,如果线程没有取到任务,则会在这里阻塞 3st = taskQueue.poll(3, TimeUnit.SECONDS);} catch (InterruptedException e) {throw new RuntimeException(e);}// 检查是否获取到任务了,如果没有则退出循环,停止运行if (t == null) {break;}t.run();}// 从线程池中移除当前线程synchronized (threadPoolMonitor) {threadPool.remove(this);}}/*** 启动内部保存的线程*/public void start() {actuallyRunningThread.start();}}/*** 构造一个线程池** @param poolSize 线程池中的线程数量*/public ThreadPool1_2(int poolSize) {this.poolSize = poolSize;}/*** 提交任务** @param task 待执行的任务*/public void submit(Runnable task) {synchronized (threadPoolMonitor) {// 如果线程数量小于最大线程数量,则新建一个线程执行任务,然后直接返回if (threadPool.size() < poolSize) {Worker worker = new Worker(task);worker.start();return;}}// 否则将任务放到任务队列中,等待空闲线程执行taskQueue.offer(task);}/*** 获取当前线程池中的线程数量** @return 当前线程池中的线程数量*/public int getCurrPoolSize() {synchronized (threadPoolMonitor) {return threadPool.size();}}
}
注:这两种实现的核心区别在于放到 threadPool
中的具体是什么对象,第一种实现将 Thread
对象放到了 threadPool
中,第二种实现将 Worker
对象放到了 threadPool
中,从而也导致了移除逻辑的不同。第二种实现不需要在移除时判断 actuallyRunningThread
是否为空,因为它移除的是 this
。
相关文章:
自定义线程池1.2
自定义线程池 1.2 1. 简介 上次我们实现了 1.1 版本,将线程池中的线程数量交给使用者决定,并且将线程的创建延迟到任务提交的时候,在本文中我们将对这个版本进行如下的优化: 在新建线程时交给线程一个任务。让线程在某种情况下…...
Spring事务传播机制有哪些?
导语: Spring事务传播机制是后端面试中的必考知识点,特别容易出现在“项目细节挖掘”阶段。面试官通过它来判断你是否真正理解事务控制的本质与异常传播机制。本文将从实战与源码角度出发,全面剖析Spring事务传播机制,帮助你答得有…...

使用ch340继电器完成随机断电测试
前言 如图所示是市面上常见的OTA压测继电器,通过ch340串口模块完成对继电器的分路控制,这里我编写了一个脚本方便对4路继电器的控制,可以设置开启时间,关闭时间,复位等功能 软件界面 在设备管理器查看串口号后&…...

基于谷歌ADK的 智能产品推荐系统(2): 模块功能详解
在我的上一篇博客:基于谷歌ADK的 智能产品推荐系统(1): 功能简介-CSDN博客 中我们介绍了个性化购物 Agent 项目,该项目展示了一个强大的框架,旨在模拟和实现在线购物环境中的智能导购。它不仅仅是一个简单的聊天机器人,更是一个集…...

VSCode 没有添加Windows右键菜单
关键字:VSCode;Windows右键菜单;注册表。 文章目录 前言一、工程环境二、配置流程1.右键文件打开2.右键文件夹打开3.右键空白处打开文件夹 三、测试总结 前言 安装 VSCode 时没有注意,实际使用的时候发现 VSCode 在 Windows 菜单栏…...

vxe-table vue 表格复选框多选数据,实现快捷键 Shift 批量选择功能
vxe-table vue 表格复选框多选数据,实现快捷键 Shift 批量选择功能 查看官网:https://vxetable.cn 效果 代码 通过 checkbox-config.isShift 启用批量选中,启用后按住快捷键和鼠标批量选取 <template><div><vxe-grid v-bind"gri…...

Android Framework预装traceroute执行文件到system/bin下
文章目录 Android SDK中寻找traceroute代码内置traceroute到SDK中traceroute参数说明-I 参数(使用 ICMP Echo 请求)-T 参数(使用 TCP SYN 包) 相关文章 Android SDK中寻找traceroute代码 设备使用的是Android 11,在/s…...

生信服务器 | 做生信为什么推荐使用Linux服务器?
原文链接:生信服务器 | 做生信为什么推荐使用Linux服务器? 一、 做生信为什么推荐使用服务器? 大家好,我是小杜。在做生信分析的同学,或是将接触学习生信分析的同学,<font style"color:rgb(53, 1…...

react-pdf(pdfjs-dist)如何兼容老浏览器(chrome 49)
之前都是使用react-pdf来渲染pdf文件,这次有个需求是要兼容xp环境,xp上chrome最高支持到49,虽然说iframe或者embed都可以实现预览pdf,但为了后续的定制化需求,还是需要使用js库来渲染。 chrome 49测试环境 能用的测试…...

RKNN开发环境搭建2-RKNN Model Zoo 环境搭建
目录 1.简介2.环境搭建2.1 启动 docker 环境2.2 安装依赖工具2.3 下载 RKNN Model Zoo2.4 RKNN模型转化2.5编译C++1.简介 RKNN Model Zoo基于 RKNPU SDK 工具链开发, 提供了目前主流算法的部署例程. 例程包含导出RKNN模型, 使用 Python API, CAPI 推理 RKNN 模型的流程. 本…...
AT模式下的全局锁冲突如何解决?
一、全局锁冲突解决方案 1. 业务层重试机制(推荐方案) Service public class OrderService {GlobalTransactionalRetryable(maxAttempts 3, backoff Backoff(delay 100))public void createOrder(OrderDTO order) {// 库存扣减(自动加全…...

20250609在荣品的PRO-RK3566开发板的Android13下解决串口可以执行命令但是脚本执行命令异常的问题
20250609在荣品的PRO-RK3566开发板的Android13下解决串口可以执行命令但是脚本执行命令异常的问题 2025/6/9 20:54 缘起,为了跨网段推流,千辛万苦配置好了网络参数。 但是命令iptables -t filter -F tetherctrl_FORWARD可以在调试串口/DEBUG口正确执行。…...
[QMT量化交易小白入门]-六十二、ETF轮动中简单的评分算法如何获取历史年化收益32.7%
本专栏主要是介绍QMT的基础用法,常见函数,写策略的方法,也会分享一些量化交易的思路,大概会写100篇左右。 QMT的相关资料较少,在使用过程中不断的摸索,遇到了一些问题,记录下来和大家一起沟通,共同进步。 文章目录 相关阅读1. 策略概述2. 趋势评分模块3 代码解析4 木头…...

21-Oracle 23 ai-Automatic SQL Plan Management(SPM)
小伙伴们,有没有迁移数据库完毕后或是突然某一天在同一个实例上同样的SQL, 性能不一样了、业务反馈卡顿、业务超时等各种匪夷所思的现状。 于是SPM定位开始,OCM考试中SPM必考。 其他的AWR、ASH、SQLHC、SQLT、SQL profile等换作下一个话题…...

性能优化中,多面体模型基本原理
1)多面体编译技术是一种基于多面体模型的程序分析和优化技术,它将程序 中的语句实例、访问关系、依赖关系和调度等信息映射到多维空间中的几何对 象,通过对这些几何对象进行几何操作和线性代数计算来进行程序的分析和优 化。 其中࿰…...

【Zephyr 系列 16】构建 BLE + LoRa 协同通信系统:网关转发与混合调度实战
🧠关键词:Zephyr、BLE、LoRa、混合通信、事件驱动、网关中继、低功耗调度 📌面向读者:希望将 BLE 和 LoRa 结合应用于资产追踪、环境监测、远程数据采集等场景的开发者 📊篇幅预计:5300+ 字 🧭 背景与需求 在许多 IoT 项目中,单一通信方式往往难以兼顾近场数据采集…...

二维数组 行列混淆区分 js
二维数组定义 行 row:是“横着的一整行” 列 column:是“竖着的一整列” 在 JavaScript 里访问二维数组 grid[i][j] 表示 第i行第j列的元素 let grid [[1, 2, 3], // 第0行[4, 5, 6], // 第1行[7, 8, 9] // 第2行 ];// grid[i][j] 表示 第i行第j列的…...

HTML版英语学习系统
HTML版英语学习系统 这是一个完全免费、无需安装、功能完整的英语学习工具,使用HTML CSS JavaScript实现。 功能 文本朗读练习 - 输入英文文章,系统朗读帮助练习听力和发音,适合跟读练习,模仿学习;实时词典查询 - 双…...

【threejs】每天一个小案例讲解:创建基本的3D场景
代码仓 GitHub - TiffanyHoo/three_practices: Learning three.js together! 可自行clone,无需安装依赖,直接liver-server运行/直接打开chapter01中的html文件 运行效果图 知识要点 核心三要素 场景(Scene) 使用 THREE.Scene(…...

C#中用于控制自定义特性(Attribute)
我们来详细解释一下 [AttributeUsage(AttributeTargets.Class, AllowMultiple false, Inherited false)] 这个 C# 属性。 在 C# 中,Attribute(特性)是一种用于向程序元素(如类、方法、属性等)添加元数据的机制。Attr…...

2025 后端自学UNIAPP【项目实战:旅游项目】7、景点详情页面【完结】
1、获取景点详情的请求【my_api.js】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http(/login/getWXSessionKey, {code,avatar}); };//…...

项目进度管理软件是什么?项目进度管理软件有哪些核心功能?
无论是建筑施工、软件开发,还是市场营销活动,项目往往涉及多个团队、大量资源和严格的时间表。如果没有一个系统化的工具来跟踪和管理这些元素,项目很容易陷入混乱,导致进度延误、成本超支,甚至失败。 项目进度管理软…...
iOS 项目怎么构建稳定性保障机制?一次系统性防错经验分享(含 KeyMob 工具应用)
崩溃、内存飙升、后台任务未释放、页面卡顿、日志丢失——稳定性问题,不一定会立刻崩,但一旦积累,就是“上线后救不回来的代价”。 稳定性保障不是某个工具的功能,而是一套贯穿开发、测试、上线全流程的“观测分析防范”机制。 …...
02-性能方案设计
需求分析与测试设计 根据具体的性能测试需求,确定测试类型,以及压测的模块(web/mysql/redis/系统整体)前期要与相关人员充分沟通,初步确定压测方案及具体的性能指标QA完成性能测试设计后,需产出测试方案文档发送邮件到项目组&…...
window 显示驱动开发-如何查询视频处理功能(三)
D3DDDICAPS_GETPROCAMPRANGE请求类型 UMD 返回指向 DXVADDI_VALUERANGE 结构的指针,该结构包含特定视频流上特定 ProcAmp 控件属性允许的值范围。 Direct3D 运行时在D3DDDIARG_GETCAPS的 pInfo 成员指向的变量中为特定视频流的 ProcAmp 控件属性指定DXVADDI_QUER…...
MySQL基本操作(续)
第3章:MySQL基本操作(续) 3.3 表操作 表是关系型数据库中存储数据的基本结构,由行和列组成。在MySQL中,表操作包括创建表、查看表结构、修改表和删除表等。本节将详细介绍这些操作。 3.3.1 创建表 在MySQL中&#…...

JUC并发编程(二)Monitor/自旋/轻量级/锁膨胀/wait/notify/锁消除
目录 一 基础 1 概念 2 卖票问题 3 转账问题 二 锁机制与优化策略 0 Monitor 1 轻量级锁 2 锁膨胀 3 自旋 4 偏向锁 5 锁消除 6 wait /notify 7 sleep与wait的对比 8 join原理 一 基础 1 概念 临界区 一段代码块内如果存在对共享资源的多线程读写操作…...
SpringCloud优势
目录 完善的微服务支持 高可用性和容错性 灵活的配置管理 强大的服务网关 分布式追踪能力 丰富的社区生态 易于与其他技术栈集成 完善的微服务支持 Spring Cloud 提供了一整套工具和组件来支持微服务架构的开发,包括服务注册与发现、负载均衡、断路器、配置管理等功能…...
Electron简介(附电子书学习资料)
一、什么是Electron? Electron 是一个由 GitHub 开发的 开源框架,允许开发者使用 Web技术(HTML、CSS、JavaScript) 构建跨平台的桌面应用程序(Windows、macOS、Linux)。它将 Chromium浏览器内核 和 Node.j…...

深入理解 C++ 左值右值、std::move 与函数重载中的参数传递
在 C 编程中,左值和右值的概念以及std::move的使用,常常让开发者感到困惑。特别是在函数重载场景下,如何合理利用这些特性来优化代码性能、确保语义正确,更是一个值得深入探讨的话题。 在开始之前,先提出几个问题&…...