如何让一个类作为可调用对象被thread调用?
如何让一个类作为可调用对象,被 std::thread 调用
在 C++ 中,可以让一个类对象作为可调用对象(Callable Object),然后用 std::thread 进行调用。要实现这一点,主要有三种方法:
- 重载
operator()(仿函数) - 使用成员函数
- 使用
std::bind或std::function
1. 方法一:重载 operator()(仿函数)
最常见的方式是定义一个仿函数(functor),即重载 operator() 使类的实例可直接调用。
示例
#include <iostream>
#include <thread>class CallableObject {
public:void operator()() { // 让对象像函数一样调用std::cout << "Thread running with CallableObject\n";}
};int main() {CallableObject obj;std::thread t(obj); // 直接将对象传给 std::threadt.join();return 0;
}
解析
CallableObject重载了operator(),因此它的实例obj可以像函数一样被调用。std::thread可以直接接受obj作为参数,调用operator()。- 线程执行
obj.operator()(),输出:Thread running with CallableObject
2. 方法二:使用类的成员函数
可以直接用类的成员函数作为 std::thread 的目标,但要注意非静态成员函数需要绑定对象。
示例
#include <iostream>
#include <thread>class Worker {
public:void run() { // 成员函数std::cout << "Thread running with member function\n";}
};int main() {Worker worker;std::thread t(&Worker::run, &worker); // 传递成员函数指针和对象t.join();return 0;
}
解析
&Worker::run取Worker的成员函数指针。&worker传入对象指针,std::thread 需要对象来调用成员函数。- 线程执行
worker.run(),输出:Thread running with member function
3. 方法三:使用 std::bind 绑定成员函数
如果 std::thread 需要额外的参数,可以用 std::bind 或 std::function 绑定成员函数和对象。
示例
#include <iostream>
#include <thread>
#include <functional>class Worker {
public:void run(int x) {std::cout << "Thread running with parameter: " << x << "\n";}
};int main() {Worker worker;std::thread t(std::bind(&Worker::run, &worker, 42)); // 绑定成员函数和参数t.join();return 0;
}
解析
std::bind(&Worker::run, &worker, 42)绑定worker.run(42)。std::thread线程启动后会执行worker.run(42)。- 输出:
Thread running with parameter: 42
4. 方法四:使用 std::function 结合 Lambda
另一种方式是用 std::function 存储可调用对象(比如 Lambda 或仿函数),然后传给 std::thread。
示例
#include <iostream>
#include <thread>
#include <functional>class Worker {
public:void run(int x) {std::cout << "Thread running with parameter: " << x << "\n";}
};int main() {Worker worker;std::function<void()> func = std::bind(&Worker::run, &worker, 42);std::thread t(func);t.join();return 0;
}
解析
std::function<void()>存储worker.run(42)。std::thread线程执行func()。- 输出:
Thread running with parameter: 42
5. 选择哪种方式?
| 方法 | 特点 | 适用场景 |
|---|---|---|
| 仿函数(operator()) | 直接使用对象,无需额外绑定 | 线程对象无需额外参数 |
| 成员函数 | 需要对象指针才能调用 | 适用于普通类成员函数 |
std::bind 绑定成员函数 | 允许传递额外参数 | 需要动态传递参数时 |
std::function | 结合 std::bind,适用于通用接口 | 适用于异步任务调度 |
对于简单任务,建议使用 重载 operator()。对于需要参数的任务,建议使用 std::bind 或 std::function。
6. 组合使用:多个线程调用类对象
可以创建多个线程,并让它们执行类中的函数:
#include <iostream>
#include <thread>
#include <vector>class Worker {
public:void run(int id) {std::cout << "Thread " << id << " is running\n";}
};int main() {Worker worker;std::vector<std::thread> threads;for (int i = 0; i < 5; ++i) {threads.emplace_back(&Worker::run, &worker, i);}for (auto& t : threads) {t.join();}return 0;
}
输出
Thread 0 is running
Thread 1 is running
Thread 2 is running
Thread 3 is running
Thread 4 is running
总结
- 让类成为可调用对象的常见方式:
- 仿函数(重载
operator()) - 成员函数(使用
&Class::method, &object绑定) - 使用
std::bind()绑定成员函数 - 使用
std::function存储可调用对象
- 仿函数(重载
- 推荐用法
- 简单的可调用对象 ➝ 仿函数
- 普通成员函数 ➝
std::thread t(&Class::method, &object) - 需要传参的成员函数 ➝
std::bind或std::function - 多个线程调用同一个对象 ➝
std::vector<std::thread>
多个线程调用同一个对象 ➝ std::vector<std::thread>
在 C++ 多线程编程中,我们经常希望多个线程同时调用同一个对象的成员函数。这通常用于:
- 并行计算(多个线程操作同一个数据对象)
- 任务调度(多个线程调用同一个处理对象)
- 服务器/多客户端模型(多个线程访问同一个服务器对象)
使用 std::vector<std::thread> 可以创建多个线程,并让它们调用同一个对象的成员函数。
1. 示例:多个线程调用同一个对象的成员函数
#include <iostream>
#include <thread>
#include <vector>class Worker {
public:void doWork(int id) {std::cout << "Thread " << id << " is working\n";}
};int main() {Worker worker; // 所有线程共享这个对象std::vector<std::thread> threads;for (int i = 0; i < 5; ++i) {threads.emplace_back(&Worker::doWork, &worker, i);}for (auto& t : threads) {t.join();}return 0;
}
可能的输出(线程执行顺序可能不同):
Thread 0 is working
Thread 1 is working
Thread 2 is working
Thread 3 is working
Thread 4 is working
2. 代码解析
-
创建
Worker对象Worker worker;- 这里
worker是所有线程共享的对象。
- 这里
-
使用
std::vector<std::thread>管理多个线程std::vector<std::thread> threads;std::vector<std::thread>存储多个std::thread对象,避免单独管理多个线程对象。
-
创建多个线程并调用
worker.doWork(i)for (int i = 0; i < 5; ++i) {threads.emplace_back(&Worker::doWork, &worker, i); }&Worker::doWork:获取Worker类的成员函数指针。&worker:传入对象指针,确保doWork在worker对象上调用。i:传入线程的 ID,作为参数。
-
等待所有线程完成
for (auto& t : threads) {t.join(); }- 遍历
threads,对每个线程调用join(),确保主线程等待所有子线程完成。
- 遍历
3. 线程安全问题
如果 Worker 类的 doWork 方法修改了成员变量,可能会发生数据竞争(Race Condition)。
示例:线程不安全
#include <iostream>
#include <thread>
#include <vector>class Worker {
private:int counter = 0; // 共享变量public:void doWork(int id) {++counter; // 线程不安全std::cout << "Thread " << id << " incremented counter to " << counter << "\n";}
};int main() {Worker worker;std::vector<std::thread> threads;for (int i = 0; i < 5; ++i) {threads.emplace_back(&Worker::doWork, &worker, i);}for (auto& t : threads) {t.join();}return 0;
}
可能的错误输出
Thread 0 incremented counter to 1
Thread 1 incremented counter to 2
Thread 2 incremented counter to 3
Thread 3 incremented counter to 2 // ❌ 竞争导致错误
Thread 4 incremented counter to 5
问题:
- 多个线程同时修改
counter,导致数据竞争(Race Condition)。 - 线程 3 可能在
counter = 2之前读取旧值,导致计数错误。
4. 解决方案:使用 std::mutex 保护数据
使用 std::mutex 保护 counter,防止数据竞争。
#include <iostream>
#include <thread>
#include <vector>
#include <mutex>class Worker {
private:int counter = 0;std::mutex mtx; // 互斥锁public:void doWork(int id) {std::lock_guard<std::mutex> lock(mtx); // 加锁++counter;std::cout << "Thread " << id << " incremented counter to " << counter << "\n";}
};int main() {Worker worker;std::vector<std::thread> threads;for (int i = 0; i < 5; ++i) {threads.emplace_back(&Worker::doWork, &worker, i);}for (auto& t : threads) {t.join();}return 0;
}
输出
Thread 0 incremented counter to 1
Thread 1 incremented counter to 2
Thread 2 incremented counter to 3
Thread 3 incremented counter to 4
Thread 4 incremented counter to 5
修改点
- 使用
std::mutex互斥锁,防止多个线程同时访问counter。 std::lock_guard<std::mutex>自动管理锁,避免手动lock()/unlock()。
5. 结论
- 多个线程可以调用同一个对象的成员函数,可以使用
std::vector<std::thread>统一管理线程。 - 如果成员函数只读,不修改成员变量,线程是安全的。
- 如果成员函数修改成员变量,必须使用
std::mutex保护数据,防止数据竞争(Race Condition)。 std::vector<std::thread>让多个线程的管理更加方便,避免单独管理多个std::thread变量。
这种模式在多线程服务器、任务分发、并行计算等场景非常常见。
相关文章:
如何让一个类作为可调用对象被thread调用?
如何让一个类作为可调用对象,被 std::thread 调用 在 C 中,可以让一个类对象作为可调用对象(Callable Object),然后用 std::thread 进行调用。要实现这一点,主要有三种方法: 重载 operator()&…...
学习小程序开发--Day1
项目学习开篇 项目架构 项目进程 创建uni-app项目 通过HBuilderX创建 小结 page.json 和 tabBar 目录文件 pages.json的配置...
“量子心灵AI“的监控仪表盘 - javascript网页设计案例
【前端实战】基于Three.js和Chart.js打造未来科技风AI监控仪表盘 本文通过AI辅助开发,详细记录了一个高级前端项目的完整实现过程。文章包含核心代码片段、技术要点及遇到的问题与解决方案。适合有一定前端基础的开发者学习参考。 1. 项目概述 本文详细介绍了一个名…...
Redis 中 string 和 list 的原理说明
Redis 中 string 和 list 的底层实现 Redis有5种基础数据结构,对应的value分别为:string (字符串)、list (列表)、set (集合)、hash (哈希) 和 zset (有序集合) Redis 对象头结构体: struct RedisObject {int4 type; // 4bits 对象的基本类型…...
JVM常用概念之String.intern()
问题 String.intern()的工作原理?我们应该如何使用它? 基础知识 字符串池(String Pool) String类在我们日常编程工作中是使用频率非常高的一种对象类型。JVM为了提升性能和减少内存开销,避免字符串的重复创建,其维…...
DeepLabv3+改进6:在主干网络中添加SegNext_Attention|助力涨点
🔥【DeepLabv3+改进专栏!探索语义分割新高度】 🌟 你是否在为图像分割的精度与效率发愁? 📢 本专栏重磅推出: ✅ 独家改进策略:融合注意力机制、轻量化设计与多尺度优化 ✅ 即插即用模块:ASPP+升级、解码器 PS:订阅专栏提供完整代码 目录 论文简介 步骤一 步骤二…...
亚信安全发布2024威胁年报和2025威胁预测
在当今数字化时代,网络空间已成为全球经济、社会和国家安全的核心基础设施。随着信息技术的飞速发展,网络连接了全球数十亿用户,推动了数字经济的蓬勃发展,同时也带来了前所未有的安全挑战。2024年,网络安全形势愈发复…...
深入理解 DOM 元素
深入理解 DOM 元素:构建动态网页的基石 在网页开发的世界里,DOM(Document Object Model,文档对象模型)元素宛如一座桥梁,连接着静态的 HTML 结构与动态的 JavaScript 交互逻辑。它让原本呆板的网页变得鲜活…...
[数据分享第七弹]全球洪水相关数据集
洪水是一种常见的自然灾害,在全球范围内造成了极为严重的威胁。近年来,针对洪水事件的检测分析,以及对于洪水灾害和灾后恢复能力的研究日渐增多,也产生了众多洪水数据集。今天,我们一起来收集整理一下相关数据集。&…...
SpringBoot POST和GET请求
1. 什么是 HTTP 请求? HTTP 协议:超文本传输协议,用于客户端和服务器之间的通信。 常见 HTTP 方法: GET:获取资源POST:提交数据PUT:更新资源DELETE:删除资源 2. GET 请求详解 作…...
MySQL 面试篇
MySQL相关面试题 定位慢查询 **面试官:**MySQL中,如何定位慢查询? 我们当时做压测的时候有的接口非常的慢,接口的响应时间超过了2秒以上,因为我们当时的系统部署了运维的监控系统Skywalking ,在展示的报表中可以看到…...
【Andrej Karpathy 神经网络从Zero到Hero】--2.语言模型的两种实现方式 (Bigram 和 神经网络)
目录 统计 Bigram 语言模型质量评价方法 神经网络语言模型 【系列笔记】 【Andrej Karpathy 神经网络从Zero到Hero】–1. 自动微分autograd实践要点 本文主要参考 大神Andrej Karpathy 大模型讲座 | 构建makemore 系列之一:讲解语言建模的明确入门,演示…...
Android MVC、MVP、MVVM三种架构的介绍和使用。
写在前面:现在随便出去面试Android APP相关的工作,面试官基本上都会提问APP架构相关的问题,用Java、kotlin写APP的话,其实就三种架构MVC、MVP、MVVM,MVC和MVP高度相似,区别不大,MVVM则不同&…...
python使用django搭建图书管理系统
大家好,你们喜欢的梦幻编织者回来了 随着计算机网络和信息技术的不断发展,人类信息交流的方式从根本上发生了改变,计算机技术、信息化技术在各个领域都得到了广泛的应用。图书馆的规模和数量都在迅速增长,馆内藏书也越来越多,管理…...
JavaScript系列06-深入理解 JavaScript 事件系统:从原生事件到 React 合成事件
JavaScript 事件系统是构建交互式 Web 应用的核心。本文从原生 DOM 事件到 React 的合成事件,内容涵盖: JavaScript 事件基础:事件类型、事件注册、事件对象事件传播机制:捕获、目标和冒泡阶段高级事件技术:事件委托、…...
大话机器学习三大门派:监督、无监督与强化学习
以武侠江湖为隐喻,系统阐述了机器学习的三大范式:监督学习(少林派)凭借标注数据精准建模,擅长图像分类等预测任务;无监督学习(逍遥派)通过数据自组织发现隐藏规律,…...
win11编译llama_cpp_python cuda128 RTX30/40/50版本
Geforce 50xx系显卡最低支持cuda128,llama_cpp_python官方源只有cpu版本,没有cuda版本,所以自己基于0.3.5版本源码编译一个RTX 30xx/40xx/50xx版本。 1. 前置条件 1. 访问https://developer.download.nvidia.cn/compute/cuda/12.8.0/local_…...
FY-3D MWRI亮温绘制
1、FY-3D MWRI介绍 风云三号气象卫星(FY-3)是我国自行研制的第二代极轨气象卫星,其有效载荷覆 盖了紫外、可见光、红外、微波等频段,其目标是实现全球全天候、多光谱、三维定量 探测,为中期数值天气预报提供卫星观测数…...
Codeforces1929F Sasha and the Wedding Binary Search Tree
目录 tags中文题面输入格式输出格式样例输入样例输出说明 思路代码 tags 组合数 二叉搜索树 中文题面 定义一棵二叉搜索树满足,点有点权,左儿子的点权 ≤ \leq ≤ 根节点的点权,右儿子的点权 ≥ \geq ≥ 根节点的点权。 现在给定一棵 …...
HBuilder X 使用 TortoiseSVN 设置快捷键方法
HBuilder X 使用 TortoiseSVN 设置快捷键方法 单文件:(上锁,解锁,提交,更新) 安装好 TortoiseSVN ,或者 按图操作: 1,工具栏中 【自定义快捷键】 2,点击 默认的快捷键设置&…...
Java jar包后台运行方式详解
目录 一、打包成 jar 文件二、后台运行 jar 文件三、示例四、总结在 Java 开发中,我们经常需要将应用程序打包成可执行的 jar 文件,并在后台运行。这种方式对于部署长时间运行的任务或需要持续监听事件的应用程序非常重要。本文将详细介绍如何实现 Java jar 包的后台运行,并…...
Refreshtoken 前端 安全 前端安全方面
网络安全 前端不需要过硬的网络安全方面的知识,但是能够了解大多数的网络安全,并且可以进行简单的防御前两三个是需要的 介绍一下常见的安全问题,解决方式,和小的Demo,希望大家喜欢 网络安全汇总 XSSCSRF点击劫持SQL注入OS注入请求劫持DDOS 在我看来,前端可以了解并且防御前…...
Mysql5.7-yum安装和更改mysql数据存放路径-2020年记录
记录下官网里用yum rpm源安装mysql, 1 官网下载rpm https://dev.mysql.com/downloads/repo/yum/ https://dev.mysql.com/doc/refman/5.7/en/linux-installation-yum-repo.html(附官网操作手册) wget https://repo.mysql.com//mysql80-community-release…...
[项目]基于FreeRTOS的STM32四轴飞行器: 七.遥控器按键
基于FreeRTOS的STM32四轴飞行器: 七.遥控器 一.遥控器按键摇杆功能说明二.摇杆和按键的配置三.按键扫描 一.遥控器按键摇杆功能说明 两个手柄四个ADC。 左侧手柄: 前后推为飞控油门,左右推为控制飞机偏航角。 右侧手柄: 控制飞机飞行方向&a…...
Android15使用FFmpeg解码并播放MP4视频完整示例
效果: 1.编译FFmpeg库: 下载FFmpeg-kit的源码并编译生成安装平台库 2.复制生成的FFmpeg库so文件与包含目录到自己的Android下 如果没有prebuiltLibs目录,创建一个,然后复制 包含目录只复制arm64-v8a下...
numpy常用函数详解
在深度神经网络代码中经常用到numpy库的一些函数,很多看过之后很容易忘记,本文对经常使用的函数进行归纳总结。 np.arange arange是numpy一个常用的函数,该函数主要用于创建等差数列。它的使用方法如下所示: numpy.arange([star…...
安装树莓派3B+环境(嵌入式开发)
一、环境配置 1、下载树莓派镜像工具 点击进入下载连接 进入网站,点击下载即可。 2、配置wifi及ssh 将SD卡插入读卡器,再接入电脑,随后打开Raspberry Pi Imager下载工具, 选择Raspberry Pi 3 选择64位的操作系统 选择SD卡 选择…...
深度学习/强化学习调参技巧
深度调优策略 1. 学习率调整 技巧:学习率是最重要的超参数之一。过大可能导致训练不稳定,过小则收敛速度慢。可以使用学习率衰减(Learning Rate Decay)或自适应学习率方法(如Adam、RMSprop)来动态调整学习…...
p5.js:sound(音乐)可视化,动画显示音频高低变化
本文通过4个案例介绍了使用 p5.js 进行音乐可视化的实践,包括将音频振幅转化为图形、生成波形图。 承上一篇:vite:初学 p5.js demo 画圆圈 cd p5-demo copy .\node_modules\p5\lib\p5.min.js . copy .\node_modules\p5\lib\addons\p5.soun…...
Linux下安装elasticsearch(Elasticsearch 7.17.23)
Elasticsearch 是一个分布式的搜索和分析引擎,能够以近乎实时的速度存储、搜索和分析大量数据。它被广泛应用于日志分析、全文搜索、应用程序监控等场景。 本文将带你一步步在 Linux 系统上安装 Elasticsearch 7.17.23 版本,并完成基本的配置࿰…...
