线程池 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任务加入到线程中处理。具体处理是共享任务队列不空时,将任务传入线程池中处理(当队列为空时,不能进行取操作);当共享任务队列为空时,并且stop
为 true
时,线程结束。(这里的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
的主要特点和用途:
通用性:
std::function
可以存储各种可调用对象,包括函数、函数指针、成员函数指针、函数对象、lambda 表达式等。类型擦除:
std::function
使用类型擦除的技术,因此可以在运行时存储和调用不同类型的可调用对象,而不需要在编译时> 确定具体类型。函数指针的包装:它可以将函数指针包装成一个对象,以便在更复杂的函数签名情况下进行传递和调用。
函数回调:
std::function
可以用于实现回调机制,允许将函数或函数对象作为参数传递给其他函数或类,以便在特定事件> 发生时执行。泛型编程:它常常用于泛型编程,使代码更具通用性,因为你可以以统一的方式处理不同的可调用对象。
一个简单的示例说明如何使用 `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
的通用性,可以容纳不同类型的可调用对象。