c++ 线程池

本文详细介绍了如何使用C++自定义线程池ThreadPool,包括enqueue方法的使用,以及std::packaged_task和线程同步机制在任务执行中的作用。讨论了线程池中任务的异步提交和线程的join操作对线程池生命周期的影响。
摘要由CSDN通过智能技术生成

手撕线程池

// 手撕线程池
#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 包装可调用对象的步骤如下:

  1. 定义可调用对象,可以是函数、函数对象或 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;
};
  1. 创建 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);
  1. 使用 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);
  1. 获取任务的结果,可以通过 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()时,容器中的所有线程对象都会被析构

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

tan code

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值