c++11总结11——半同步半异步线程池

线程池要需要解决的问题

传统的方式处理大并发任务时,一个请求对应一个线程来处理,大量的线程创建和销毁会消耗过多的系统资源,还增加了线程上下文切换的开销,线程池技术就是来处理这个问题的。

线程池

线程池技术通过在系统中预先创建一定数量的线程,当任务请求到来时从线程池中分配一个预先创建好的线程来处理任务,线程在处理完任务后还可以重用,不会被销毁,而是等待下一次任务的到来。

线程池的优点

1. 对于多核处理器,由于线程会被分配到多个CPU,会提高并行处理的效率;

2. 每个线程独立阻塞,可以防止主线程被阻塞,从而使主流程被阻塞,导致其他请求无法得到响应;

线程池的分类

线程池主要分为半同步半异步线程池和领导者追随者线程池。本次主要介绍半同步半异步线程池。

半同步半异步线程池结构

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

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

3. 异步服务层:存在多个线程同时处理排队层中的任务,异步服务层从同步排队层中取出任务并行处理。

半同步半异步线程池流程图

同步队列添加任务流程图:

同步队列取任务流程图:

 

注意: 同步排队层中对添加任务的数目上限做了限制,当任务数到达上限时,不会让上层的任务添加进来,而是等待已有任务处理,避免内存暴涨等问题。

示例代码

#include <iostream>
#include <mutex>
#include <thread>
#include <list>
#include <condition_variable>
#include <atomic>
#include <memory>
#include <functional>
#include <utility>
#include <chrono>
using namespace std;

const int MaxTaskCount = 100;

//同步队列
template <typename T>
class SyncQueue
{
public:
	SyncQueue(int iMaxSize)
	{
		m_iMaxSize = iMaxSize;
	}

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

	void Put(T&& x)
	{
		Add(std::forward<T>(x));
	}

	void Take(std::list<T>& list)
	{
		std::unique_lock<std::mutex> lock(m_mutex);
		m_notEmpty.wait(lock, [this] {
			return m_bStop || NotEmpty();
		});                                          //如条件不满足,线程置为waiting状态

		if (m_bStop)
			return;

		list = std::move(m_queue);    //获取同步队列
		m_notFull.notify_one();
	}

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

		if (m_bStop)
			return;

		t = m_queue.front();
		m_queue.pop_front();
		m_notFull.notify_one();
	}

	void Stop()
	{
		{
			std::unique_lock<std::mutex> lock(m_mutex);
			m_bStop = true;
		}

		m_notFull.notify_all();
		m_notEmpty.notify_all();
	}

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

	bool Full()
	{
		std::unique_lock<std::mutex> lock(m_mutex);
		return m_queue.size() == m_iMaxSize;
	}

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

	int Count()
	{
		return m_queue.size();
	}

private:
	bool NotFull() const
	{
		bool full = m_queue.size() >= m_iMaxSize;
		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&& f)
	{
		std::unique_lock<std::mutex> lock(m_mutex);
		m_notFull.wait(lock, [this] {
			return m_bStop || NotFull();
		});

		m_queue.push_back(std::forward<F>(f));     //将新任务插入队列中
		m_notEmpty.notify_one();                   //唤醒取任务线程去取数据
	}

private:
	std::list<T>              m_queue;                  //缓冲区
	std::mutex                m_mutex;                  //互斥量和条件变量结合起来使用
	std::condition_variable   m_notEmpty;               //不为空的条件变量(线程池线程不为空)
	std::condition_variable   m_notFull;                //没有满的条件变量(线程池任务没有满)

	int                       m_iMaxSize = 0;           //同步队列最大的数目
	bool                      m_bStop = false;          //停止的标志
};

//线程池
class ThreadPool
{
public:
	using Task = std::function<void()>;
	ThreadPool(int numThreads = std::thread::hardware_concurrency()) : 
		m_queue(MaxTaskCount)
	{

	}

	~ThreadPool(void)
	{
		//如果没有停止则主动停止线程池
		Stop();
	}

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

	//添加任务
	void AddTask(Task&& task)
	{
		m_queue.Put(std::forward<Task>(task));
	}

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

	//开始创建线程
	void Start(int numThreads)
	{
		m_running = true;

		for (int i = 0; i < numThreads; i++)
		{
			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;     //内部线程跳出循环并推出

		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;                                
};

int main()
{
	ThreadPool pool;

	std::thread thd1([&pool] {
		for (int i = 0; i < 10; i++)
		{
			auto thdId = this_thread::get_id();
			pool.AddTask([thdId] {
				cout << "同步层线程1的线程ID是:" << thdId << endl;
			});
		}
	});

	std::thread thd2([&pool] {
		for (int i = 0; i < 10; i++)
		{
			auto thdId = this_thread::get_id();
			pool.AddTask([thdId] {
				cout << "同步层线程2的线程ID是:" << thdId << endl;
			});
		}
	});

	this_thread::sleep_for(std::chrono::seconds(2));
	pool.Start(2);
	thd1.join();
	thd2.join();

	system("pause");
    return 0;
}

执行结果:

知识扩展

1. condition_variable

当condition_variable 对象的某个wait函数被调用时,它使用 unique_lock(通过std::mutex) 来锁住当前线程。当前线程会一直被阻塞,直到另外一个线程在相同的condition_variable对象上调用了 notification 函数来唤醒当前线程。

1.1    std::condition_variable::wait() 

原型: void wait(unique_lock<mutex>& lck);

当前线程调用 wait()后将被阻塞(此时当前线程应该获得了锁(mutex),不妨设获得锁 lck),直到另外某个线程调用 notify_* 唤醒了当前线程。

1.2    std::condition_variable::notify_one() 

唤醒某个等待的线程。如果当前没有等待线程,则该函数什么都不做,如果同时存在多个等待线程,则唤醒某个线程是不确定的。

1.3    std::condition_variable::notify_all() 

唤醒所有等待的线程。如果当前没有等待线程,则该函数什么都不做。

2. 命名空间 std::this_thread

以下来自http://www.cplusplus.com/解释:

This thread

This namespace groups a set of functions that access the current thread.

Functions

get_id

Get thread id (获取当前线程ID)

yield

Yield to other threads

sleep_until

Sleep until time point

sleep_for

Sleep for time span 

 

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值