如何让一个类作为可调用对象被thread调用?

news2025/3/10 14:57:58

如何让一个类作为可调用对象,被 std::thread 调用

在 C++ 中,可以让一个类对象作为可调用对象(Callable Object),然后用 std::thread 进行调用。要实现这一点,主要有三种方法:

  1. 重载 operator()(仿函数)
  2. 使用成员函数
  3. 使用 std::bindstd::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::thread
    t.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::runWorker成员函数指针
  • &worker 传入对象指针,std::thread 需要对象来调用成员函数
  • 线程执行 worker.run(),输出:
    Thread running with member function
    

3. 方法三:使用 std::bind 绑定成员函数

如果 std::thread 需要额外的参数,可以用 std::bindstd::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::bindstd::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

总结

  • 让类成为可调用对象的常见方式:
    1. 仿函数(重载 operator()
    2. 成员函数(使用 &Class::method, &object 绑定)
    3. 使用 std::bind() 绑定成员函数
    4. 使用 std::function 存储可调用对象
  • 推荐用法
    • 简单的可调用对象仿函数
    • 普通成员函数std::thread t(&Class::method, &object)
    • 需要传参的成员函数std::bindstd::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. 代码解析

  1. 创建 Worker 对象

    Worker worker;
    
    • 这里 worker所有线程共享的对象
  2. 使用 std::vector<std::thread> 管理多个线程

    std::vector<std::thread> threads;
    
    • std::vector<std::thread> 存储多个 std::thread 对象,避免单独管理多个线程对象。
  3. 创建多个线程并调用 worker.doWork(i)

    for (int i = 0; i < 5; ++i) {
        threads.emplace_back(&Worker::doWork, &worker, i);
    }
    
    • &Worker::doWork:获取 Worker 类的成员函数指针。
    • &worker:传入对象指针,确保 doWorkworker 对象上调用。
    • i:传入线程的 ID,作为参数。
  4. 等待所有线程完成

    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 变量。

这种模式在多线程服务器、任务分发、并行计算等场景非常常见。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2312751.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

学习小程序开发--Day1

项目学习开篇 项目架构 项目进程 创建uni-app项目 通过HBuilderX创建 小结 page.json 和 tabBar 目录文件 pages.json的配置

“量子心灵AI“的监控仪表盘 - javascript网页设计案例

【前端实战】基于Three.js和Chart.js打造未来科技风AI监控仪表盘 本文通过AI辅助开发&#xff0c;详细记录了一个高级前端项目的完整实现过程。文章包含核心代码片段、技术要点及遇到的问题与解决方案。适合有一定前端基础的开发者学习参考。 1. 项目概述 本文详细介绍了一个名…

Redis 中 string 和 list 的原理说明

Redis 中 string 和 list 的底层实现 Redis有5种基础数据结构&#xff0c;对应的value分别为&#xff1a;string (字符串)、list (列表)、set (集合)、hash (哈希) 和 zset (有序集合) Redis 对象头结构体&#xff1a; struct RedisObject {int4 type; // 4bits 对象的基本类型…

DeepLabv3+改进6:在主干网络中添加SegNext_Attention|助力涨点

🔥【DeepLabv3+改进专栏!探索语义分割新高度】 🌟 你是否在为图像分割的精度与效率发愁? 📢 本专栏重磅推出: ✅ 独家改进策略:融合注意力机制、轻量化设计与多尺度优化 ✅ 即插即用模块:ASPP+升级、解码器 PS:订阅专栏提供完整代码 目录 论文简介 步骤一 步骤二…

亚信安全发布2024威胁年报和2025威胁预测

在当今数字化时代&#xff0c;网络空间已成为全球经济、社会和国家安全的核心基础设施。随着信息技术的飞速发展&#xff0c;网络连接了全球数十亿用户&#xff0c;推动了数字经济的蓬勃发展&#xff0c;同时也带来了前所未有的安全挑战。2024年&#xff0c;网络安全形势愈发复…

[数据分享第七弹]全球洪水相关数据集

洪水是一种常见的自然灾害&#xff0c;在全球范围内造成了极为严重的威胁。近年来&#xff0c;针对洪水事件的检测分析&#xff0c;以及对于洪水灾害和灾后恢复能力的研究日渐增多&#xff0c;也产生了众多洪水数据集。今天&#xff0c;我们一起来收集整理一下相关数据集。&…

MySQL 面试篇

MySQL相关面试题 定位慢查询 **面试官&#xff1a;**MySQL中&#xff0c;如何定位慢查询? 我们当时做压测的时候有的接口非常的慢&#xff0c;接口的响应时间超过了2秒以上&#xff0c;因为我们当时的系统部署了运维的监控系统Skywalking &#xff0c;在展示的报表中可以看到…

【Andrej Karpathy 神经网络从Zero到Hero】--2.语言模型的两种实现方式 (Bigram 和 神经网络)

目录 统计 Bigram 语言模型质量评价方法 神经网络语言模型 【系列笔记】 【Andrej Karpathy 神经网络从Zero到Hero】–1. 自动微分autograd实践要点 本文主要参考 大神Andrej Karpathy 大模型讲座 | 构建makemore 系列之一&#xff1a;讲解语言建模的明确入门&#xff0c;演示…

Android MVC、MVP、MVVM三种架构的介绍和使用。

写在前面&#xff1a;现在随便出去面试Android APP相关的工作&#xff0c;面试官基本上都会提问APP架构相关的问题&#xff0c;用Java、kotlin写APP的话&#xff0c;其实就三种架构MVC、MVP、MVVM&#xff0c;MVC和MVP高度相似&#xff0c;区别不大&#xff0c;MVVM则不同&…

python使用django搭建图书管理系统

大家好,你们喜欢的梦幻编织者回来了 随着计算机网络和信息技术的不断发展&#xff0c;人类信息交流的方式从根本上发生了改变&#xff0c;计算机技术、信息化技术在各个领域都得到了广泛的应用。图书馆的规模和数量都在迅速增长&#xff0c;馆内藏书也越来越多&#xff0c;管理…

JavaScript系列06-深入理解 JavaScript 事件系统:从原生事件到 React 合成事件

JavaScript 事件系统是构建交互式 Web 应用的核心。本文从原生 DOM 事件到 React 的合成事件&#xff0c;内容涵盖&#xff1a; JavaScript 事件基础&#xff1a;事件类型、事件注册、事件对象事件传播机制&#xff1a;捕获、目标和冒泡阶段高级事件技术&#xff1a;事件委托、…

大话机器学习三大门派:监督、无监督与强化学习

以武侠江湖为隐喻&#xff0c;系统阐述了机器学习的三大范式&#xff1a;​监督学习&#xff08;少林派&#xff09;​凭借标注数据精准建模&#xff0c;擅长图像分类等预测任务&#xff1b;无监督学习&#xff08;逍遥派&#xff09;​通过数据自组织发现隐藏规律&#xff0c;…

win11编译llama_cpp_python cuda128 RTX30/40/50版本

Geforce 50xx系显卡最低支持cuda128&#xff0c;llama_cpp_python官方源只有cpu版本&#xff0c;没有cuda版本&#xff0c;所以自己基于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介绍 风云三号气象卫星&#xff08;FY-3&#xff09;是我国自行研制的第二代极轨气象卫星&#xff0c;其有效载荷覆 盖了紫外、可见光、红外、微波等频段&#xff0c;其目标是实现全球全天候、多光谱、三维定量 探测&#xff0c;为中期数值天气预报提供卫星观测数…

Codeforces1929F Sasha and the Wedding Binary Search Tree

目录 tags中文题面输入格式输出格式样例输入样例输出说明 思路代码 tags 组合数 二叉搜索树 中文题面 定义一棵二叉搜索树满足&#xff0c;点有点权&#xff0c;左儿子的点权 ≤ \leq ≤ 根节点的点权&#xff0c;右儿子的点权 ≥ \geq ≥ 根节点的点权。 现在给定一棵 …

HBuilder X 使用 TortoiseSVN 设置快捷键方法

HBuilder X 使用 TortoiseSVN 设置快捷键方法 单文件&#xff1a;(上锁&#xff0c;解锁&#xff0c;提交&#xff0c;更新) 安装好 TortoiseSVN &#xff0c;或者 按图操作&#xff1a; 1&#xff0c;工具栏中 【自定义快捷键】 2&#xff0c;点击 默认的快捷键设置&…

Java jar包后台运行方式详解

目录 一、打包成 jar 文件二、后台运行 jar 文件三、示例四、总结在 Java 开发中,我们经常需要将应用程序打包成可执行的 jar 文件,并在后台运行。这种方式对于部署长时间运行的任务或需要持续监听事件的应用程序非常重要。本文将详细介绍如何实现 Java jar 包的后台运行,并…

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&#xff08;附官网操作手册&#xff09; wget https://repo.mysql.com//mysql80-community-release…

[项目]基于FreeRTOS的STM32四轴飞行器: 七.遥控器按键

基于FreeRTOS的STM32四轴飞行器: 七.遥控器 一.遥控器按键摇杆功能说明二.摇杆和按键的配置三.按键扫描 一.遥控器按键摇杆功能说明 两个手柄四个ADC。 左侧手柄&#xff1a; 前后推为飞控油门&#xff0c;左右推为控制飞机偏航角。 右侧手柄&#xff1a; 控制飞机飞行方向&a…

Android15使用FFmpeg解码并播放MP4视频完整示例

效果: 1.编译FFmpeg库: 下载FFmpeg-kit的源码并编译生成安装平台库 2.复制生成的FFmpeg库so文件与包含目录到自己的Android下 如果没有prebuiltLibs目录,创建一个,然后复制 包含目录只复制arm64-v8a下