实现C++ 11 线程池

实现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 个任务会被分配到不同的线程中执行。在任务执行完成后,程序会退出。

补充知识:

  1. this 指针
    在 C++ 中,this 是一个指向当前对象实例的指针。它在类的成员函数中隐式可用,用于访问该对象的成员变量和成员函数。在你的代码中,this 被捕获到 lambda 表达式中,使得 lambda 表达式能够访问 ThreadPool 类的成员变量和成员函数。
  2. Lambda 表达式
    Lambda 表达式是一种匿名函数,可以在代码中定义并立即使用。它的基本语法如下:
capture -> return_type {
    // function body
}
  • 捕获列表 [capture]:指定 lambda 表达式可以捕获哪些变量。可以捕获外部作用域中的变量,使得 lambda表达式能够在其内部使用这些变量。
  • 参数列表 (parameters):与普通函数的参数列表类似,指定 lambda 表达式的参数。
  • 返回类型 -> return_type:可选,指定 lambda 表达式的返回类型。如果省略,编译器会自动推断。
  • 函数体 { … }:lambda 表达式的主体,包含要执行的代码
  1. 右值引用
    https://www.cnblogs.com/KillerAery/p/12802771.html
    这个更清晰

① 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&& fArgs&&... 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 函数可以接受任意数量和类型的参数,并将它们打印出来。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值