手撕线程池
// 手撕线程池
#include <iostream>
#include <vector>
#include <queue>
#include <thread>
#include <future>
#include <functional>
#include <mutex>
#include <condition_variable>
class ThreadPool {
public:
ThreadPool(size_t numThreads);
~ThreadPool();
template<typename F, typename... Args>
auto enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type>; // 用于向线程池中添加任务
private:
std::vector<std::thread> _workers;
std::queue<std::function<void()>> _tasks;
std::mutex _queueMutex;
std::condition_variable _condition;
bool _stop;
};
inline ThreadPool::ThreadPool(size_t numThreads) : _stop(false) {
for (size_t i = 0; i < numThreads; ++i) {
_workers.emplace_back([this] {
while (true) {
std::function<void()> task;
{
std::unique_lock<std::mutex> lock(this->_queueMutex);
this->_condition.wait(lock, [this] { return this->_stop || !this->_tasks.empty(); });
if (this->_stop && this->_tasks.empty()) {
return;
}
task = std::move(this->_tasks.front());
this->_tasks.pop();
}
task();
}
});
}
}
template <typename F, typename... Args>
auto ThreadPool::enqueue(F&& f, Args&&... args)
-> std::future<typename std::result_of<F(Args...)>::type> {
using return_type = typename std::result_of<F(Args...)>::type;
auto task = std::make_shared<std::packaged_task<return_type()>>(
std::bind(std::forward<F>(f), std::forward<Args>(args)...)
);
std::future<return_type> res = task->get_future();
{
std::unique_lock<std::mutex> lock(_queueMutex);
if (_stop) {
throw std::runtime_error("enqueue on stopped ThreadPool");
}
_tasks.emplace([task]() { (*task)(); });
}
_condition.notify_all();
return res;
}
inline ThreadPool::~ThreadPool() {
{
std::unique_lock<std::mutex> lock(_queueMutex);
_stop = true;
}
_condition.notify_all();
for (std::thread& worker : _workers) {
worker.join();
}
}
int main() {
ThreadPool pool(4);
std::vector<std::future<int>> results;
for (int i = 0; i < 8; ++i) {
results.emplace_back(pool.enqueue([i] {
std::cout << "Task " << i << " executed" << std::endl;
return i * i;
}));
}
for (auto& result : results) {
std::cout << "Result: " << result.get() << std::endl;
}
return 0;
}
测试运行分析
g++ -std=c++11 -pthread -o output_file threadPool.cc
结果
[root@VM-8-14-centos tan]# ./output_file
Result: Task Task 20 executed executed
Task 3 executed
Task 4 executed
Task 5 executed
Task Task 1 executed
Task 7 executed
6 executed
0
Result: 1
Result: 4
Result: 9
Result: 16
Result: 25
Result: 36
Result: 49
Task部分出现了串行,原因是,加入任务和获取任务,这两个操作,通过加锁来保护,但执行任务时,是多线程并行的,所以不是同步的。
语法说明
std::packaged_task
用于包装可调用对象(如函数、函数对象、Lambda 表达式等)并将其与 future 关联起来。它允许在一个线程中异步执行任务,并在另一个线程中获取任务的结果。
使用 std::packaged_task 包装可调用对象的步骤如下:
- 定义可调用对象,可以是函数、函数对象或 Lambda 表达式。
int sum(int a, int b) {
return a + b;
}
struct Functor {
int operator()(int a, int b) {
return a + b;
}
};
auto lambda = [](int a, int b) {
return a + b;
};
- 创建 std::packaged_task 对象,指定可调用对象的类型作为模板参数,并将可调用对象作为构造函数的参数。
std::packaged_task<int(int, int)> task1(sum);
std::packaged_task<int(int, int)> task2(Functor());
std::packaged_task<int(int, int)> task3(lambda);
- 使用 std::packaged_task 的 operator() 方法执行任务,并将其放入一个线程中执行。
std::thread t1(std::move(task1), 1, 2);
std::thread t2(std::move(task2), 3, 4);
std::thread t3(std::move(task3), 5, 6);
- 获取任务的结果,可以通过 std::packaged_task 的 get_future() 方法获取一个 std::future 对象,并使用其 get() 方法获取任务的结果。
std::future<int> future1 = task1.get_future();
std::cout << "Result 1: " << future1.get() << std::endl;
std::future<int> future2 = task2.get_future();
std::cout << "Result 2: " << future2.get() << std::endl;
std::future<int> future3 = task3.get_future();
std::cout << "Result 3: " << future3.get() << std::endl;
需要注意的是,std::packaged_task 只能执行一次任务,执行完毕后,就无法再次执行。如果需要重复执行任务,可以创建多个 std::packaged_task 对象。
此外,std::packaged_task 还可以与 std::async、std::thread、std::bind 等其他多线程相关的类和函数一起使用,实现更复杂的多线程任务处理。
为什么join线程就可以析构线程池了
在C++中,std::thread::join
会阻塞当前线程,直到被join
的线程完成执行。这确保了线程完成了它的任务。
在代码中,for (std::thread& worker : _workers) { worker.join(); }
这段代码会等待线程池中的所有线程完成执行。
一旦所有的线程都已经完成执行并被join
,线程对象就处于可析构的状态,可以安全地清除或销毁它们。在代码中,如果_workers
是一个容器(std::vector
),那么当离开作用域或者显式地调用_workers.clear()
时,容器中的所有线程对象都会被析构