Rex-LK / ThreadPool

A simple C++ Thread Pool implementation

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

线程池介绍

作为五大池之一(内存池、连接池、线程池、进程池、协程池),线程池的应用非常广泛,不管事客户端程序还是后台服务端,都是提高业务处理能力的必备模块。

线程池有很多的开源实现,虽然在接口使用上优点区别,但是其核心实现原理基本相同。

线程池知识背景

  • C++面向对象的标准
    • 组合和继承、多态、STL容器、智能指针、函数对象、绑定器、可变参数模板等
  • C++11多线程编程
    • thread、mutex、atomic、condition_variable、unique_lock等
  • C++17和C++20标准
    • C++17中的any类型可以接受任何类型的参数、C++20 的semaphore信号量
  • 多线程理论
    • 多线程基本知识、线程互斥、线程同步、原子操作等。

并发和并行

  • 并发:单核上,多个线程占用不同的CPU时间片,物理上还是串行执行的,但是由于每个线程占用的CPU时间片非常短(比如10ms),看起来就像是多个线程都在共同执行一样,这样的场景称作并发(concurrent)。

  • 并行:在多核或者多CPU上,多个线程是在真正的同时执行,这样的场景称作并行(parallel)。

线程池的优势

多线程程序一定就好吗?不一定,要看具体的应用场景:

  • 多线程处理可以同时运⾏多个线程。由于多线程应⽤程序将程序划分成多个独⽴的任务,因此可以在以下⽅⾯显著提⾼性能: (1)多线程技术使程序的响应速度更快 ,因为⽤户界⾯可以在进⾏其它⼯作的同时⼀直处于活动状态; (2)当前没有进⾏处理的任务时可以将处理器时间让给其它任务; (3)占⽤⼤量处理时间的任务可以定期将处理器时间让给其它任务; (4)可以随时停⽌任务; (5)可以分别设置各个任务的优先级以优化性能。

  • 多线程开发的缺点: 同样的 ,多线程也存在许多缺点 ,在考虑多线程时需要进⾏充分的考虑。多线程的主要缺点包括: (1)等候使⽤共享资源时造成程序的运⾏速度变慢。这些共享资源主要是独占性的资源 ,如打印机等。 (2)对线程进⾏管理要求额外的 CPU开销。线程的使⽤会给系统带来上下⽂切换的额外负担。当这种负担超过⼀定程度时,多线程的特点主要 表现在其缺点上,⽐如⽤独⽴的线程来更新数组内每个元素。 (3)线程的死锁。即较长时间的等待或资源竞争以及死锁等多线程症状。 (4)对公有变量的同时读或写。当多个线程需要对公有变量进⾏写操作时,后⼀个线程往往会修改掉前⼀个线程存放的数据,从⽽使前⼀个线程 的参数被修改;另外 ,当公⽤变量的读写操作是⾮原⼦性时,在不同的机器上,中断时间的不确定性,会导致数据在⼀个线程内的操作产⽣错误,从⽽产⽣莫名其妙的错误,⽽这种错误是程序员⽆法预知的。

  • fixed模式线程池 线程池里面的线程个数是固定不变的,一般是ThreadPool创建时根据当前机器的CPU核心数量进行指 定。 cached模式线程池 线程池里面的线程个数是可动态增长的,根据任务的数量动态的增加线程的数量,但是会设置一个线程 数量的阈值(线程过多的坏处上面已经讲过了),任务处理完成,如果动态增长的线程空闲了60s还没 有处理其它任务,那么关闭线程,保持池中最初数量的线程即可。fixed模式线程池: 线程池里面的线程个数是固定不变的,一般是ThreadPool创建时根据当前机器的CPU核心数量进行指 定。

  • cached模式线程池: 线程池里面的线程个数是可动态增长的,根据任务的数量动态的增加线程的数量,但是会设置一个线程 数量的阈值(线程过多的坏处上面已经讲过了),任务处理完成,如果动态增长的线程空闲了60s还没 有处理其它任务,那么关闭线程,保持池中最初数量的线程即可。

两个版本的线程池,第一个版本自己定义的类型较多;第二个版本多采用C++新特性提供的类型

第一个版本

线程池两个模式:

//线程池支持的模式
enum class PoolMode
{
	MODE_FIXED,		//固定数量的线程
	MODE_CACHED,	//线程数量可以动态增长
};

C++17提供了Any类型 可以接受任何参数,我们第一个版本手写一个Any类型用于接受任何参数:

//定义一个Any类型 可以接受任何数据类型
//C++17中提供了Any类型 可以保存任何数据类型
class Any
{
public:
	Any() = default;
	~Any() = default;
	Any(const Any&) = delete;
	Any& operator=(const Any&) = delete;
	Any(Any&&) = default;
	Any& operator=(Any&&) = default;

	//构造成函数 可以接受任何数据类型
	template<typename T>
	Any(T data) :m_base(std::make_unique<Derive<T>>(data))
	{
	}

	template<typename T>
	T cast()		//用来提取保存的数据
	{
		//先将保存的基类转化为子类
		Derive<T>* dp = dynamic_cast<Derive<T>*>(m_base.get());
		if (dp == nullptr)
		{
			throw "type is wrong";
		}
		return dp->m_data;
	}

private:
	class Base		//基类类型
	{
	public:
		virtual ~Base() = default;
	};

	template<typename T>
	class Derive :public Base		//派生类类型
	{
	public:
		Derive(T data) :m_data(data)
		{
		}
		T m_data;	//可以保存所有数据类型
	};

private:
	//定义一个基类指针
	std::unique_ptr<Base> m_base;
};

C++20提供了semaphore信号量 但是之前的版本没有提供 下面我们手写一个信号量:

//定义一个信号量 Semaphore在C++20中已经提供
class Semaphore
{
public:
	//构造函数
	Semaphore(int limit = 0)
		:m_resLimit(limit)
		, m_isExit(false)
	{

	}

	~Semaphore()
	{
		m_isExit = true;
	}

	//获取一个信号量资源
	void wait()
	{
		if (m_isExit)
			return;
		std::unique_lock<std::mutex> lock(m_mtx);
		//等待信号量有资源,没有资源的话 阻塞当前线程
		m_cond.wait(lock, [&]()->bool {return m_resLimit > 0; });
		m_resLimit--;
	}

	//增加一个信号量资源
	void post()
	{
		if (m_isExit)
			return;
		std::unique_lock<std::mutex> lock(m_mtx);
		m_resLimit++;
		m_cond.notify_all();
	}

private:
	std::atomic_bool m_isExit;
	int m_resLimit;
	std::mutex m_mtx;
	std::condition_variable m_cond;
};

线程类型类:

//线程类型
class Thread
{
public:
	//线程函数对象类型
	using ThreadFunc = std::function<void(int)>;
	// 线程构造
	Thread(ThreadFunc func);
	// 线程析构
	~Thread();
	// 启动线程
	void start();

	//获取线程ID
	int getId()const;
private:
	ThreadFunc m_func;
	static int m_generateId;
	int m_threadId;		//保存线程id
};

线程池类型:

//线程池类型
class ThreadPool
{
public:
	//线程池构造
	ThreadPool();
	//线程池析构
	~ThreadPool();

	//设置线程池的工作模式
	void setMode(PoolMode mode);

	//设置task任务队列上线的阈值
	void setTaskQueMaxThrshHold(int threshhold);
	//给线程池提交任务
	Result submitTask(std::shared_ptr<Task> sp);

	//设置线程池cached模式下线程阈值
	void setThreadSizeThreshHold(int threshHold);

	//开启线程池
	void start(int initThreadSize = std::thread::hardware_concurrency());

	ThreadPool(const ThreadPool&) = delete;
	ThreadPool& operator=(const ThreadPool&) = delete;

private:
	//定义线程函数
	void threadFunc(int threadId);

	//检查pool的运行状态
	bool checkRunningState() const;
private:
	//std::vector<std::unique_ptr<Thread>> m_threads;	//线程列表
	std::unordered_map <int, std::unique_ptr<Thread>> m_threads;
	//初始的线程数量
	int m_initThreadSize;

	//记录当前线程池里面线程的总数量
	std::atomic_int m_curThreadSize;

	//线程数量上限阈值
	int m_threadSizeThreshHold;

	//记录空闲线程的数量
	std::atomic_int m_idleThreadSize;

	//任务队列
	std::queue<std::shared_ptr<Task>> m_taskque;

	//任务的数量
	std::atomic_int m_taskSize;
	//任务队列数量上限的阈值
	int m_taskqueMaxThresHold;

	//包装任务队列的线程安全
	std::mutex m_taskQueMtx;

	//表示任务队列不满
	std::condition_variable m_notFull;
	//表示任务队列不空
	std::condition_variable m_notEmpty;

	//表示等待线程资源全部回收
	std::condition_variable m_exitCond;

	//当前线程池的工作模式
	PoolMode m_poolMode;

	//表示当前线程池的启动状态
	std::atomic_bool m_isPoolRunning;
};

测试类型(main.cpp):

#include "ThreadPool.h"

#include <thread>
#include <chrono>
#include <iostream>

using ulong = unsigned long long;

class MyTask:public Task
{
public:
	MyTask(int begin, int end)
		:m_begin(begin),
		m_end(end)
	{
	}
	Any run()
	{
		std::cout << "tid:" << std::this_thread::get_id()
			<< "begin..." << std::endl;
		//std::this_thread::sleep_for(std::chrono::seconds(2));
		ulong sum = 0;
		for (int i = m_begin; i <= m_end; i++)
		{
			sum += i;
		}
		std::cout << "tid:" << std::this_thread::get_id()
			<< "end..." << std::endl;
		return sum;
	}
private:
	int m_begin;
	int m_end;
};

int main()
{
	{
		ThreadPool pool;
		pool.setMode(PoolMode::MODE_CACHED);
		// 开始启动线程池
		pool.start(2);

		// linux上,这些Result对象也是局部对象,要析构的!!!
		Result res1 = pool.submitTask(std::make_shared<MyTask>(1, 100000000));
		Result res2 = pool.submitTask(std::make_shared<MyTask>(100000001, 200000000));
		pool.submitTask(std::make_shared<MyTask>(100000001, 200000000));
		pool.submitTask(std::make_shared<MyTask>(100000001, 200000000));
		pool.submitTask(std::make_shared<MyTask>(100000001, 200000000));

		ulong sum1 = res1.get().cast<ulong>();
		std::cout << sum1 << std::endl; 
	} // 这里Result对象也要析构!!! 在vs下,条件变量析构会释放相应资源的

	std::cout << "main over!" << std::endl;
	getchar();
}

结果:

第二个版本

线程池支持的模式:

enum class PoolMode
{
	MODE_FIXED,		//固定数量的线程
	MODE_CACHED,	//线程数量可以动态增长
};

线程类型:

class Thread
{
public:
	//线程函数对象类型
	using ThreadFunc = std::function<void(int)>;
	// 线程构造
	Thread(ThreadFunc func);
	// 线程析构
	~Thread() = default;
	// 启动线程
	void start();

	//获取线程ID
	int getId()const;
private:
	ThreadFunc m_func;
	static int m_generateId;
	int m_threadId;		//保存线程id
};

测试类型(main.cpp):

#include "thread.h"

#include <future>
#include <functional>
#include <thread>
#include <chrono>

int sum1(int a, int b)
{
	std::this_thread::sleep_for(std::chrono::seconds(1));
	return a + b;
}

int sum2(int a, int b, int c)
{
	std::this_thread::sleep_for(std::chrono::seconds(1));
	return a + b + c;
}

int main()
{
	ThreadPool pool;
	pool.start(4);

	std::future<int> res1 = pool.submit(sum1, 100, 300);
	std::future<int> res2 = pool.submit(sum2, 1, 2, 3);

	std::cout << res1.get() << std::endl;
	std::cout << res2.get() << std::endl;
}

测试结果:

About

A simple C++ Thread Pool implementation


Languages

Language:C++ 100.0%