C++11实现的简单线程池、模板的使用实例:1.向队列中放待执行函数,2.取出队列中待执行函数

线程池:创建几个线程用于处理任务,这些线程暂时不销毁,从而减少线程创建和销毁所需的时间。将任务放进任务队列中,线程从任务队列中取任务。这是一个生成者和消费者模型,需要考虑互斥与同步的问题。实现所需内容如下:

一个锁:用于线程互斥访问任务队列
两个条件变量:

  • 1.当任务队列满时,此时生产者线程阻塞。当任务队列不满时,此时通知生产者线程添加任务。
  • 2.当任务队为空时,此时消费者线程阻塞。当任务队列不为空时,此时通知消费者线程处理任务。

循环队列:用循环队列实现任务队列

#include <iostream>
#include <vector>
#include <queue>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <future>


class ThreadPool {
public:
    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(queueMutex);
                        condition.wait(lock, [this] { return stop || !tasks.empty(); });
                        if (stop && tasks.empty()) {
                            return;
                        }
                        task = std::move(tasks.front());
                        tasks.pop();
                    }
                    task();
                }
            });
        }
    }

    template<typename F, typename... Args>
    auto 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> result = task->get_future(); // 返回一个futur对象,result通过result.get()获取线程函数的返回值 如果线程函数没有执行完就会阻塞在result.get()
        {
            std::unique_lock<std::mutex> lock(queueMutex);
            if (stop) {
                throw std::runtime_error("enqueue on stopped ThreadPool");
            }
            tasks.emplace([task]() { (*task)(); }); // Lambda函数是一种可调用对象 Lambda函数的语法为[捕获列表](参数列表) { 函数体 }。
        }
        condition.notify_one();
        return result;
    }

    ~ThreadPool() {
        {
            std::unique_lock<std::mutex> lock(queueMutex);
            stop = true;
        }
        condition.notify_all();
        for (std::thread& worker : workers) {
            worker.join();
        }
    }

private:
    std::vector<std::thread> workers;
    std::queue<std::function<void()>> tasks;
    std::mutex queueMutex;
    std::condition_variable condition;
    bool stop;
};

// 示例任务函数
int  printHello(int num) {
    std::cout << "Hello from thread " << std::this_thread::get_id() << "! Num: " << num << std::endl;
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return num;
}

int main() {
    ThreadPool pool(4);

    std::vector<std::future<int>> results; 

    // 提交任务到线程池
    std::cout << "Enqueuing tasks..." << std::endl;
    for (int i = 0; i < 8; ++i) {
        results.push_back(pool.enqueue(printHello, i));
    }

    for (int i = 0; i < 8; ++i) {
        int num = results[i].get(); // 获取线程函数的返回值
        std::cout<< "获取到线程函数的返回值:" << num << std::endl;    // 获取线程函数的返回值  
    }

    // 等待任务完成
    std::this_thread::sleep_for(std::chrono::seconds(5));

    return 0;
}

如果上面代码的模板部分可以参考如下链接进行学习:
C++可变参数
std::future、std::promise、std::packaged_task、std::async
C++ Lambda表达式详解
右值与完美转发

  • std::future<typename std::result_of<F(Args...)>::type>的typename用于告诉编译器后面的标识符是一个类型名而不是一个变量名或函数名。解释:这个表达式中,std::result_of<F(Args...)>::type并不是直接的类型名,而是一个依赖类型(dependent type),它依赖于模板参数F和Args...的具体实例化。由于依赖类型的名称可能在编译器实例化时发生变化,因此在使用依赖类型时,需要使用typename关键字来指示编译器将其解析为类型名。如下直接类型名就不需要使用typename:
#include <iostream>

struct MyStruct {
    using MyType = int;
};

int main() {
    // typename MyStruct::MyType variable = 10; // 正确
    MyStruct::MyType variable = 10; // 正确
    std::cout << variable << std::endl;

    return 0;
}
  • std::packaged_task<return_type()>return_type()代表的是参数为空,返回值为return_type类型的函数。

  • ThreadPool()的匿名函数将this作为捕获列表,此时就可以直接使用对象中的成员变量,甚至都不需要this->queueMutex,只需要queueMutex就可以访问对象中的成员变量。

posted @ 2023-07-10 14:26  好人~  阅读(91)  评论(0编辑  收藏  举报