C++11 线程池(二)

一、半同步半异步线程池介绍
1、

    同步服务1              同步服务2               同步服务3
	    |                      |                       |
		  |                    |                     |
	   Read/Write          Read/Write            Read/Write
	        |                  |                   |
			  |                |                 |
			    |              |               |
				             队列
							   |
							   |
							Dequeue
							   |
							   |
							异步服务 <----消息到来---- 外部事件源

2、 第一层:同步服务层
它处理来自上层的任务请求,上层的请求可能是并发的,这些请求不是马上就会被处理,而是将这些任务放到一个同步排队层中,等待处理。

3、 第二层:同步排队层
来自上层的任务请求都会加到排队层中等待处理。

4、 第三层:异步服务层
这一层中会有很多个预先创建好的线程,从同步排队层中取出任务,并行处理这些任务。

5、 对于上层来说,只要将任务丢到同步队列中就行,至于谁去处理,什么时候处理都不用关心,主线程也不会阻塞,还能继续发起新的请求。

二、线程池实现的关键技术分析
1、 排队层就是一个同步队列,居于核心地位,上层将任务加到排队层中,异步服务层取出任务,这是一个同步的过程,同步队列要保证操作过程是安全的。

2、 线程池有两个活动记录:
(1)、往同步队列中添加任务(同步服务层)。
(2)、从同步队列中取出任务(异步服务层)。

3、 往同步队列中添加任务(同步服务层)的活动图:

                   开始
				    |
					|
				   \|/
			 队列是否达到上限 ----是----> 等待任务减少
			        |                           |
					|                           |
				    否                          |
				    |                           |
				   \|/                         \|/     
				 添加任务 <----------- 直到队列没有达到上限
				    |
					|
				   \|/
				   结束

4、 从同步队列中取出任务(异步服务层)的活动图:

                   开始
				    |
				    |
				   \|/
			启动一定数量的线程
			        |
				    |
				   \|/
				 是否停止 ----是---->等待每个线程退出 ----> 结束
				    |
					|
				   否
				    |
				   \|/
				 轮巡队列
				    |
				    |
				   \|/
			 同步队列是否为空 ----是----> 等待任务
			        |                        |
					|                        |
					否                       |
					|                        |
				   \|/                      \|/
		从队列中取出一个任务执行 <----直到不为空时激活

三、同步队列
1、 同步队列即为同步排队层。

2、 同步队列的主要作用:
(1)、保证队列中共享数据线程安全。
(2)、为同步服务层提供添加新任务的接口。
(3)、为异步服务层提供取任务的接口。
(4)、限制任务数的上限,避免任务过多导致内存暴涨的问题。

3、 同步队列的锁:用于线程同步。

4、 同步队列的条件变量:用来线程通信。

5、 同步队列空了异步服务层就等待,不为空就通知一个线程去处理;同步队列满了同步服务层就等待,不满就通知上层添加新的任务。

6、 代码:

#pragma once

#include <list>
#include <mutex>
#include <thread>
#include <condition_variable>
#include <iostream>

using namespace std;


//同步队列存放任务,但是任务类型不唯一,所以用模板。
template <typename T>
class SyncQueue
{
public:
	SyncQueue(int maxSize = 1024) : m_maxSize(maxSize), m_needStop(false) {}

	void Put(const T & x)
	{
		Add(x);
	}

	void Put(T && x)
	{
		//完美转发,保留x的左、右值属性。
		Add(std::forward<T>(x));  
	}

	//一次加锁,取出队列中所有数据,大大减少加锁的次数。
	void Take(std::list<T> & list)
	{
		std::unique_lock<std::mutex> locker(m_mutex);
		m_notEmpty.wait(locker, [this] {return m_needStop || NotEmpty(); });

		if (m_needStop)
		{
			return;
		}

		//通过移动,避免数据的复制
		list = std::move(m_queue);
		m_notFull.notify_one();
	}

	void Take(T & t)
	{
		std::unique_lock<std::mutex> locker(m_mutex);
		m_notEmpty.wait(locker, [this] {return m_needStop || NotEmpty(); });

		if (m_needStop)
		{
			return;
		}

		t = m_queue.front();
		m_queue.pop_front();

		m_notFull.notify_one();
	}

	void Stop()
	{
		{
			std::lock_guard<std::mutex> locker(m_mutex);
			//等待的条件变量都有m_needStop条件,满足m_needStop==true就表示满足条件。
			m_needStop = true;
		}

		//放在互斥锁外面,是为了优化。
		//因为被唤醒的条件变量需要获得互斥锁,才能继续往下运行。
		//如果这两句唤醒语句被locker保护着,就得先等到std::lock_guard析构才会释放m_mutex。
		//所以放在外面性能会好一点。
		m_notFull.notify_all();
		m_notEmpty.notify_all();
	}

	bool Empty()
	{
		std::lock_guard<std::mutex> locker(m_mutex);
		return m_queue.empty();
	}

	bool Full()
	{
		std::lock_guard<std::mutex> locker(m_mutex);
		return m_queue.size() == m_maxSize;
	}

	size_t Size()
	{
		std::lock_guard<std::mutex> locker(m_mutex);
		return m_queue.size();
	}

	int Count()
	{
		std::lock_guard<std::mutex> locker(m_mutex);
		return m_queue.size();
	}

private:
	bool NotFull() const
	{
		bool full = m_queue.size() >= m_maxSize;
		if (full)
		{
			cout << "缓冲区满了,需要等待..." << endl;
		}

		return !full;
	}

	bool NotEmpty() const
	{
		bool empty = m_queue.empty();
		if (empty)
		{
			cout << "缓冲区空了,需要等待...异步层的线程ID:" << this_thread::get_id() << endl;
		}

		return !empty;
	}

	template <typename F>
	void Add(F && x)
	{
		std::unique_lock<std::mutex> locker(m_mutex);
		m_notFull.wait(locker, [this] {return m_needStop || NotFull(); });

		if (m_needStop)
		{
			return;
		}

		m_queue.push_back(std::forward<F>(x));
		m_notEmpty.notify_one();
	}

private:
	int m_maxSize;                         //同步队列最大的size
	bool m_needStop;                       //停止的标志

	std::list<T> m_queue;                  //缓冲区,存放任务
	std::mutex m_mutex;                    //互斥量和条件变量结合使用
	std::condition_variable m_notEmpty;    //不为空的条件变量
	std::condition_variable m_notFull;     //没有满的条件变量
};

四、线程池
1、 一个完整的线程池包括三层,同步服务层、排队层、异步服务层,这也是一种生产者-消费者模型。

2、 线程池功能结构分析:
同步服务层是生产者,不断将新任务丢到排队层中,因此线程池需提供一个添加新任务的接口。
异步服务层是消费者,其中预先创建的线程去处理排队层中的任务,一般建议创建CPU核数的线程以达到最优的效率。
排队层是同步队列,它内部保证了上下两层对共享数据的安全访问,同时保证不会被无限制地添加任务导致内存暴涨。
线程池还得提供一个停止的接口,让用户能够在需要的时候停止线程池的运行。

3、 代码:

#pragma once

#include <list>
#include <thread>
#include <functional>
#include <memory>
#include <atomic>
#include "SyncQueue.hpp"


const int MaxTaskCount = 100;


class ThreadPool
{
public:
	//任务其实就是一个函数体。
	using Task = std::function<void()>;

	//std::thread::hardware_concurrency()获取CPU核心数量
	ThreadPool(int numThreads = std::thread::hardware_concurrency()) : m_queue(MaxTaskCount)
	{
		printf("create thread num: %d\n", numThreads);
		Start(numThreads);
	}

	~ThreadPool()
	{
		Stop();
	}

	void Stop()
	{
		//保证多线程情况下只调用一次StopThreadGroup
		std::call_once(m_flag, [this] {StopThreadGroup(); });
	}

	//如果主线程调用AddTask,则同步队列满时主线程会阻塞,直到同步队列不满。
	//如果其它线程调用AddTask,则主线程可以不用阻塞。
	void AddTask(Task && task)
	{
		m_queue.Put(std::forward<Task>(task));
	}

	void AddTask(const Task & task)
	{
		m_queue.Put(task);
	}

private:
	void Start(int numThreads)
	{
		m_running = true;

		for (int i = 0; i < numThreads; i++)
		{
			//创建线程组,这些线程都是joinable状态,都是ready状态,随时可能被操作系统执行
			m_threadgroup.push_back(std::make_shared<std::thread>(&ThreadPool::RunInThread, this));
		}
	}

	//异步服务层中线程自己的函数体,各线程各自阻塞!!!
	void RunInThread()
	{
		while (m_running)
		{
			//取任务执行,有任务则取出来,无任务则阻塞。
			std::list<Task> list;
			m_queue.Take(list);

			for (auto & task : list)
			{
				if (!m_running)
					return;

				task();
			}
		}
	}


	void StopThreadGroup()
	{
		m_queue.Stop();

		m_running = false;

		//将所有线程的状态都变为nonjoinable,否则退出会崩溃。
		for (auto thread : m_threadgroup)
		{
			if (thread)
			{
				thread->join();
			}
		}

		m_threadgroup.clear();
	}

private:
	std::list<std::shared_ptr<std::thread>> m_threadgroup;     //处理任务的线程组
	SyncQueue<Task>                         m_queue;           //同步队列
	atomic_bool                             m_running;         //是否停止的标志
	std::once_flag                          m_flag;            //std::call_once的参数
};

五、测试代码:

#include "ThreadPool.hpp"

ThreadPool pool;


//子线程t1的线程函数,添加10个任务。
void func1()
{
	for (int i = 0; i < 10; i++)
	{
		auto thdId = this_thread::get_id();
		pool.AddTask([thdId] {cout << "同步层线程1的线程ID: " << thdId << endl; });
	}
}


//子线程t2的线程函数,添加10个任务。
void func2()
{
	for (int i = 0; i < 10; i++)
	{
		auto thdId = this_thread::get_id();
		pool.AddTask([thdId] {cout << "同步层线程2的线程ID: " << thdId << endl; });
    }
}


int main()
{
	thread t1(func1);
	thread t2(func2);

	getchar();

	//停止线程池
	pool.Stop();
	//将t1和t2的状态置为nonjoinable
	t1.join();
	t2.join();

	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

程序员小马兰

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值