如何让一个类作为可调用对象,被 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
变量。
这种模式在多线程服务器、任务分发、并行计算等场景非常常见。