线程池 3(第三部分--线程池主体设计)

这一部分主要就是线程池的实现,然后写一个对外接口,将任务提交到任务队列,然后用多线程理任务。(这一部分主要内容就是任务函数的返回类型解析,多入参函数的解析,要确保多个入参可以被线程池解析出来,并且执行)。

这里的函数主要用到的是 <functional> 这个类,利用std::bind()将函数和参数进行绑定。参数使用c++11的新特性,可变参数模板。就可以实现。

class ThreadPool
{
public:
	//线程池构造函数
	ThreadPool();
	//1, 提交函数
	template<typename F, typename... Args>
	auto Enqueue(F&& f, Args &&...args)->std::future<decltype(f(args...))> ;

	// 析构函数
	inline ~ThreadPool();

private:
	// 线程池
	std::vector<std::thread> worker_;
	// 任务队列
	BoundedQueue<std::function<void()>>  task_queue_;
	// 退出线程池标志位
	std::atomic_bool stop_;

};

1、构造函数

//线程池构造函数
	explicit  ThreadPool(std::size_t thread_mun, std::size_t max_task_num = 1000) : stop_(false)
	{
		// 初始化失败抛出异常
		if (!task_queue_.Init(max_task_num, new SleepWaitStratrgy)) {	// SleepWaitStratrgy  BlockWaitStrategy
			throw std::runtime_error("Task queue init failed");
		}

		// 存放多个 std::thread线程对象
		worker_.reserve(thread_mun);
		for (size_t i = 0; i < thread_mun; i++)
		{
			// 使用lamdba 表达式来创建每个线程
			// 功能是 从任务队列中获取任务,并且执行任务的函数对象
			worker_.emplace_back([this] {
				while (!stop_)
				{
					std::function<void()> task;
					if (task_queue_.WaitDequeue(&task)) {
						task();
					}
				}
			});
		}
	}

1、对任务队列进行初始化。

2、修改一个线程对象大小。

3、在线程队列中,构造线程,并且轮流从线程池中用WaitDequeue(&task)函数,获取任务。并且运行任务。


2、提交任务函数

template<typename F, typename... Args>
	auto Enqueue(F&& f, Args &&...args)->std::future<decltype(f(args...))> {

		std::function<decltype(f(args...))()> func = 
             std::bind(std::forward<F>(f), std::forward<Args>(args)...);

		auto task = 
            std::make_shared<std::packaged_task<decltype(f(args...))()>>(func);

		// 任务队列的入队   并且唤醒线程
		task_queue_.Enqueue([task]() {(*task)();});

		return task->get_future();
	}

这个也是核心关键部分。

1、头部函数模板是利用的,可变参数模板来接受多个参数。

template<typename F, typename... Args>

2、这个头部函数名

auto Enqueue(F&& f, Args &&...args)->std::future<decltype(f(args...))> 

auto:动推导函数类型。

Enqueue(F&& f, Args &&...args):两个入参,一个是万能引用,一个是可变参数模板。(这里的&&不是右值引用,万能引用可以简单理解为,当T是模板参数时,T&&的作用主要是保持值类别进行转发。)

->std::future<decltype(f(args...))> :c++11中关键字decltype解决了auto关键字只能对变量类型进行类型推导的缺陷。用于弥补auto,不能直接推导函数类型。

std::future<>:异步推导,需要用到的时候再调用结果,不需要函数阻塞再原地,等待decltype推导函数类型。

3、

std::function<decltype(f(args...))()> func = 
             std::bind(std::forward<F>(f), std::forward<Args>(args)...);

定义一个func   类型是通用函数模板类,用decltype去推导函数类型。再由std::function 中的bind函数,将函数和其参数进行绑定。这里用到了std::forward() 完美转发,是因为我们这里的 “ f ”,传过来的时候不是右值,是一个"引用",然而,一个绑定到  完美引用 上的对象可能具 是个左值 或者也可能是个右值,正是因为有这种二义性,所以产生了std::forward

4、

auto task = std::make_shared<std::packaged_task<decltype(f(args...))()>>(func);

这里我们使用了std::make_shared方法,声明了一个std::packaged_task<decltype(f(args...))()>类型的智能指针,并且将前面的 func 作为std::package_task的参数传入。智能指针更方便的我们对package_task进行管理。

package_tas可以用来封装任何可以调用的目标,从而用于实现异步的调用。

5、

task_queue_.Enqueue([task]() {(*task)();});

return task->get_future();

任务入队,然后调用,task的get_future() 获取异步结果的方法。

我们返回了一个 future 对象,因此我们可以将其返回给调用者,由调用者自行决定何时调用 get() 方法获取异步操作的结果


3、析构函数

	// 析构函数
	inline ~ThreadPool() {
		// 防止重复析构, (将stop_改变成true,并且返回之前的值)
		if (stop_.exchange(true)) {
			return;
		}
		// 线程池中所有线程停止
		task_queue_.BreakAllWait();
		// 并且等待他们完成所有任务
		for (std::thread& worker : worker_)
		{
			worker.join();
		}
	}

这里的析构基本上就是基本的函数析构逻辑,解放所有的资源。

到此为止,整个线程池也就全部结束了。

 参考:基于C++11实现线程池 - 知乎

猜你喜欢

转载自blog.csdn.net/qq_35326529/article/details/130916737