实现C++ 11 线程池
#include <iostream>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <functional>
#include <queue>
#include <vector>
// 线程池(可扩展为单例模式
class ThreadPool {
public:
ThreadPool(int numThreads) :stop(false) { //构造函数:加入的线程的数量,标识符初始化为false
for (int i = 0; i < numThreads; i++) {
threads.emplace_back([this] {
while (1) {
// 线程循环
std::unique_lock<std::mutex> lock(mtx);
// 线程会一直处于等待状态,当队列不为空时和线程终止的时候结束等待
condition.wait(lock, [this] {
return !tasks.empty() || stop;
});
if (stop && tasks.empty()) {
return;
};
//线程有任务,则进一步去取任务
std::function<void()> task(std::move(tasks.front())); //取任务
tasks.pop(); //取到后把它从原来任务队列中删除
lock.unlock();
task();
}
});
}
}
~ThreadPool() {
{
std::unique_lock<std::mutex> lock(mtx); //stop是一个共享变量,所以需要对其进行加锁
stop = true;
}
//stop为true的时候通知任务队列把所有任务完成
condition.notify_all();
for (auto& t : threads) {
t.join();
}
}
template<class F, class... Args>
void enqueue(F&& f, Args&&... args) {
std::function<void()> task =
std::bind(std::forward<F>(f), std::forward<Args>(args)...);
// 对共享变量进行操作的时候要加锁
std::unique_lock<std::mutex> lock(mtx);
// 把任务加到任务队列中,如果它是左值的话转化成右值传进去
tasks.emplace(std::move(task));
// 有任务进来让条件变量速速去完成
condition.notify_one();
}
private:
std::vector<std::thread> threads; //一个动态数组存在多个线程对象
std::queue <std::function<void()>> tasks; //一个队列存在多个function对象
std::mutex mtx; //添加一个互斥锁
std::condition_variable condition; //当有新任务添加到队列中时,条件变量会被通知,从而唤醒一个等待的线程来处理任务
bool stop; //标识线程池终止
};
int main() {
ThreadPool pool(4);
for (int i = 0; i < 10; i++) {
pool.enqueue([i] {
std::cout << "task:" << i << "is runing" << std::endl;
std::this_thread::sleep_for(std::chrono::seconds(1));
std::cout << "task:" << i << "is done" << std::endl;
});
}
return 0;
}
在这个示例中,我们同样定义了一个 ThreadPool 类,并且在构造函数中创建了指定数目的线程。在每个线程中,我们不断地从任务队列中获取任务并执行,直到线程池被停止。在 enqueue() 函数中,我们将任务封装成一个 std::function 对象,并将它添加到任务队列中。在 ThreadPool 的析构函数中,我们等待所有线程执行完成后再停止所有线程。
在主函数中,我们创建了一个 ThreadPool 对象,并向任务队列中添加了 8 个任务。每个任务会输出一些信息,并且在执行完后等待 1 秒钟。由于线程池中有 4 个线程,因此这 8 个任务会被分配到不同的线程中执行。在任务执行完成后,程序会退出。
补充知识:
- this 指针
在 C++ 中,this 是一个指向当前对象实例的指针。它在类的成员函数中隐式可用,用于访问该对象的成员变量和成员函数。在你的代码中,this 被捕获到 lambda 表达式中,使得 lambda 表达式能够访问 ThreadPool 类的成员变量和成员函数。 - Lambda 表达式
Lambda 表达式是一种匿名函数,可以在代码中定义并立即使用。它的基本语法如下:
capture -> return_type {
// function body
}
- 捕获列表 [capture]:指定 lambda 表达式可以捕获哪些变量。可以捕获外部作用域中的变量,使得 lambda表达式能够在其内部使用这些变量。
- 参数列表 (parameters):与普通函数的参数列表类似,指定 lambda 表达式的参数。
- 返回类型 -> return_type:可选,指定 lambda 表达式的返回类型。如果省略,编译器会自动推断。
- 函数体 { … }:lambda 表达式的主体,包含要执行的代码
① lambda 表达式 和 线程 的结合
threads.emplace_back([this]{...});
threads.emplace_back([this]{ ... });
:这是在向 threads 向量中添加一个新的线程。emplace_back 方法会直接在容器中构造对象,避免了不必要的拷贝或移动操作。[this]
:这是捕获列表,表示 lambda 表达式可以捕获当前对象的 this 指针,从而可以访问类的成员变量和成员函数。{ ... }
:这是 lambda 表达式的主体,包含了线程执行的代码。
② lambda 实现线程同步
condition.wait(lock, [this] { return stop || !tasks.empty(); });
这是条件变量 condition 的 wait 方法,它会使当前线程等待,直到满足特定条件。
- lock:这是一个
std::unique_lock<std::mutex>
对象,用于在等待期间锁定互斥锁 mutex。 [this] { return stop || !tasks.empty(); }
:这是一个 lambda 表达式,作为条件变量的谓词。它返回一个布尔值,表示线程是否应该继续等待。
具体来说,当 stop 为 true 或者 tasks 队列不为空时,lambda 表达式返回 true,线程将继续执行;否则,线程将继续等待。
这行代码的作用是让线程在以下两种情况下被唤醒:
- stop 标志被设置为 true,表示线程池需要停止。
- tasks 队列中有任务可执行。
③ 模板声明
template<typename F, typename... Args>
是一个模板声明,表示这是一个 可变参数模板。让我们详细解释一下:
模板声明
template<typename F, typename... Args>
:这是模板的声明部分,告诉编译器接下来定义的函数或类是一个模板。
typename F
:表示模板参数 F,可以是任何类型。
typename... Args
:表示一个模板参数包 Args,可以包含零个或多个类型参数。
可变参数模板
可变参数模板(Variadic Templates)是 C++11 引入的一种特性,允许模板接受可变数量的参数。它们可以用于函数模板和类模板。
示例解释
在代码中:
template<typename F, typename... Args>
void enqueue(F&& f, Args&&... args) {
std::function<void()> task(std::bind(std::forward<F>(f), std::forward<Args>(args)...));
{
std::unique_lock<std::mutex> lock(mutex);
tasks.emplace(std::move(task));
}
condition.notify_one();
}
template<typename F, typename... Args>
:声明了一个函数模板 enqueue,它接受一个可变数量的模板参数。
F&& f
和 Args&&... args
:函数参数列表,使用了 完美转发(Perfect Forwarding),允许将传入的参数原封不动地传递给其他函数。
std::function<void()> task(std::bind(std::forward<F>(f), std::forward<Args>(args)...))
:创建一个 std::function<void()>
类型的任务,通过 std::bind
将函数 f 和参数 args 绑定在一起,并使用 std::forward
完成完美转发。
示例
以下是一个简单的可变参数模板示例:
#include <iostream>
// 可变参数模板函数
template<typename... Args>
void print(Args... args) {
(std::cout << ... << args) << std::endl; // 使用折叠表达式打印所有参数
}
int main() {
print(1, 2, 3, "Hello", 4.5); // 打印:1 2 3 Hello 4.5
return 0;
}
在这个示例中,print 函数可以接受任意数量和类型的参数,并将它们打印出来。