C++多线程——线程池

线程池 Thread Pool


线程池简单来说就是用来管理多个线程的,以避免频繁开辟或销毁线程的情况。

以下是创建和使用线程池的基本步骤:

  • 创建一个任务队列,用于存储待执行的任务。

  • 创建一组线程,这些线程会从任务队列中获取任务并执行它们。

  • 将任务提交到任务队列中,由线程池的线程异步执行。

  • 线程池会不断地从任务队列中获取任务并执行,直到没有任务可执行。

C++标准库并没用提供内置的线程池实现,我们可以利用共享队列、std::thread 类型的vector数组、条件变量、互斥锁来完成。

以下是具体实现:

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

class ThreadPool {
public:
    ThreadPool(size_t numThreads) {
        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();
                }
            });
        }
    }

    void enqueueTask(std::function<void()> task) {
        {
            std::unique_lock<std::mutex> lock(queueMutex);
            tasks.emplace(std::move(task));
        }
        condition.notify_one();
    }

    ~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 = false;
};

int main() {
    ThreadPool pool(4); // 创建一个具有4个线程的线程池

    // 提交任务给线程池
    for (int i = 0; i < 8; ++i) {
        pool.enqueueTask([i] {
            std::this_thread::sleep_for(std::chrono::seconds(1));
            std::cout << "Task " << i << " executed by thread " << std::this_thread::get_id() << std::endl;
        });
    }

    // 在任务执行完之前等待
    std::this_thread::sleep_for(std::chrono::seconds(5));

    return 0;
}


解释:
在线程池 ThreadPool 的构造函数中,我们传入了一个 numThrads 变量作为开辟线程的数量。实现起来也就是往 std::thread 的类型 vector 数组中添加 numThreads 个线程。在实现时,并不是先创建一个线程,再将线程通过 workers.push_back() 加入vector数组中,而是使用了效率更高的 workers.emplace_back() 方法。这个传入的参数不再是 std::thread 类型,而是 std::thread 类型构造函数参数。也就是 std::thread 执行的函数。std::thread 在这个地方执行的函数内容应该是发现tasks队列不空时,将task任务加入到线程中处理。具体处理是共享任务队列不空时,将任务传入线程池中处理(当队列为空时,不能进行取操作);当共享任务队列为空时,并且stoptrue时,线程结束。(这里的stop会在析构函数中变为 true,意味着当调用ThreadPool 的析构函数时,线程就应该都乖乖结束了)。enqueueTask(std::function<void()> task) 方法是将task 任务加入共享队列中,加入后利用条件变量通知(告诉线程池的构造函数中的workers数组,可以继续干活了)。在线程池销毁时,也就是调用线程池的析构函数时,会将 stop 设置为true,并且通知所有线程干活+join方法使得活干完

std::vector::emplace_back

std::vector::emplace_back 是 C++ 标准库中的一个函数,用于将元素添加到 std::vector 容器的末尾。与 push_back 不同,emplace_back 允许你在容器中构造元素,而不是先创建一个元素然后再将其复制到容器中。这可以提高性能,尤其是当元素类型是自定义类时。

emplace_back 的语法如下:

vector.emplace_back(args);

其中:

  • vector 是你的 std::vector 容器。
  • args 是构造元素的参数,可以是构造函数所需的参数。

一个使用 emplace_back 将自定义类的对象添加到 std::vector的示例:

演示 `emplace_back` 将自定义类的对象添加到 `std::vector`的示例:
#include <iostream>
#include <vector>

class MyClass {
public:
    MyClass(int a, double b) : value1(a), value2(b) {
        std::cout << "Constructor called" << std::endl;
    }

private:
    int value1;
    double value2;
};

int main() {
    std::vector<MyClass> myVector;

    // 使用 emplace_back 构造 MyClass 的对象并添加到 vector
    myVector.emplace_back(42, 3.14);

    // 使用 emplace_back 添加另一个对象
    myVector.emplace_back(100, 2.71);

    return 0;
}

在上面的示例中,emplace_back 允许你直接传递构造函数所需的参数,并在容器中构造对象。这避免了创建临时对象,从而提高了性能。注意,在构造时输出的 "Constructor called" 语句表明对象是在调用 emplace_back 时构造的。


std::function

std::function 是 C++ 标准库提供的一个通用函数封装类,它可以用来存储、传递和调用各种可调用对象(函数、函数指针、成员> 函数指针、lambda 表达式等)。std::function 提供了一种通用的方式来保存函数对象,使其可以在运行时动态确定调用的函数。

以下是 std::function 的主要特点和用途:

  1. 通用性std::function 可以存储各种可调用对象,包括函数、函数指针、成员函数指针、函数对象、lambda 表达式等。

  2. 类型擦除std::function 使用类型擦除的技术,因此可以在运行时存储和调用不同类型的可调用对象,而不需要在编译时> 确定具体类型。

  3. 函数指针的包装:它可以将函数指针包装成一个对象,以便在更复杂的函数签名情况下进行传递和调用。

  4. 函数回调std::function 可以用于实现回调机制,允许将函数或函数对象作为参数传递给其他函数或类,以便在特定事件> 发生时执行。

  5. 泛型编程:它常常用于泛型编程,使代码更具通用性,因为你可以以统一的方式处理不同的可调用对象。

一个简单的示例说明如何使用 `std::function`
#include <iostream>
#include <functional>

// 定义一个函数类型
using MyFunction = std::function<void(int)>;

void printNumber(int number) {
    std::cout << "Number: " << number << std::endl;
}

int main() {
    MyFunction func = printNumber; // 将函数指针包装成 std::function 对象
    func(42); // 调用包装的函数

    // 使用 lambda 表达式创建 std::function 对象
    MyFunction lambdaFunc = [](int x) {
        std::cout << "Lambda: " << x << std::endl;
    };
    lambdaFunc(123);

    return 0;
}

在这个示例中,我们首先将一个普通函数 printNumber 包装成 std::function 对象,并调用它。然后,我们使用 lambda 表达式创建另一个 std::function 对象,也调用它。这演示了 std::function 的通用性,可以容纳不同类型的可调用对象。

posted @ 2023-10-30 16:20  vLiion  阅读(512)  评论(0编辑  收藏  举报