肃宁县网站建设,中国建筑集团有限公司怎么样,装修方案,wordpress网站如何播放视频目录
1.关于线程池的基本理论
1.1.线程池是什么#xff1f;
1.2.线程池的应用场景#xff1a;
2.线程池的基本架构
2.1.线程容器
2.2.任务队列
2.3.线程函数#xff08;HandlerTask#xff09;
2.4.线程唤醒机制
3.添加单例模式
3.1.单例模式是什么
1.2.线程池的应用场景
2.线程池的基本架构
2.1.线程容器
2.2.任务队列
2.3.线程函数HandlerTask
2.4.线程唤醒机制
3.添加单例模式
3.1.单例模式是什么
3.2.饿汉实现方式和懒汉实现方式
饿汉式单例模式
懒汉式单例模式
3.3.改写懒汉式的单例模式
双判断的方式为什么能减少单例的加锁成本呢
单判断为什么会出错
单例模式的注意点
4.代码和执行效果 1.关于线程池的基本理论
1.1.线程池是什么
一种线程使用模式。线程过多会带来调度开销进而影响缓存局部性和整体性能。而线程池维护着多个线程等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。
1.2.线程池的应用场景 需要大量的线程来完成任务且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务使用线程池技术是非常合适的。因为单个任务小而任务数量巨大你可以想象一个热门网站的点击次数。 但对于长时间的任务比如一个Telnet连接请求线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。 对性能要求苛刻的应用比如要求服务器迅速响应客户请求。接受突发性的大量请求但不至于使服务器因此产生大量线程的应用。突发性大量客户请求在没有线程池情况下将产生大量线程虽然理论上大部分操作系统线程数目最大值不是问题短时间内产生大量线程可能使内存到达极限出现错误.
2.线程池的基本架构
线程容器用来管理创建的线程方便统一初始化。任务队列用来储存任务消息需要支持压入与取出的操作。线程函数HandlerTask线程都需要执行这个函数模块在这个函数模块中进行任务的等待和执行。线程唤醒机制需要一个线程唤醒机制通过条件变量和互斥锁完成对线程的保护与唤醒。单例模式线程池不需要创建多个一个程序只需要一个线程池通过单例模式进行优化。
2.1.线程容器
我们使用vector容器来存储线程并且使用自己封装的线程来实现线程使用的各个接口 std::vectorThread _threads; 2.2.任务队列
我们使用队列这个容器来存储任务并且利用队列FIFO的特性进行存储任务和取出任务 std::queueT _task_queue; 2.3.线程函数HandlerTask
我们首先要明确线程需要死循环去执行任务所以需要while一直循环直到线程池已经退出了任务队列是空的。执行任务的同时还需要保证线程的安全所以需要加锁来保证。 void HandlerTask(std::string name){LOG(INFO, %s is running..., name.c_str());//线程需要死循环去处理任务while(true){//1、保证队列安全LockQueue();//2、队列中不一定有数据while(_task_queue.empty() _isrunning){_waitnum;ThreadSleep();_waitnum--;}//2.1 如果线程池已经退出了任务队列是空的if(_task_queue.empty() !_isrunning){UnlockQueue();break;}// 2.2 如果线程池不退出 任务队列不是空的// 2.3 如果线程池已经退出 任务队列不是空的 --- 处理完所有的任务然后在退出// 3. 一定有任务, 处理任务T t _task_queue.front();_task_queue.pop();UnlockQueue();LOG(DEBUG, %s get a task, name.c_str());//4.处理任务这个任务属于线程独占的任务//t();LOG(DEBUG, %s handler a task, result is: %s, name.c_str(), t.ResultToString().c_str());}}2.4.线程唤醒机制
需要一个线程唤醒机制通过条件变量加互斥锁完成对线程的保护与唤醒。
3.添加单例模式
3.1.单例模式是什么
某些类, 只应该具有一个对象(实例), 就称之为单例。在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据.
3.2.饿汉实现方式和懒汉实现方式
饿汉式单例模式
饿汉式单例模式在类加载时就完成了实例的创建。这种方式的特点是线程安全因为 JVM 在加载类时会对静态变量进行初始化并且这个过程是线程互斥的。
template typename T
class Singleton {
static T data;
public:
static T* GetInstance() {
return data;
}
};
缺点程序启动的时候可能会很慢所以我们一般不用饿汉
懒汉式单例模式
template typename T
class Singleton {
static T* inst;
public:
static T* GetInstance() {
if (inst NULL) {
inst new T();
}
return inst;
}
};
缺点存在一个严重的问题, 线程不安全. 第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例. 但是后续再次调用, 就没有问题了.
其实在日常使用中我们一般不会使用饿汉式单例模式因为它启动的时候过慢所以我们来改写基于懒汉式的单例模式主要解决线程安全的问题
3.3.改写懒汉式的单例模式
添加双判断来解决线程安全问题。 static ThreadPoolT *GetInstance(){// 如果是多线程获取线程池对象下面的代码就有问题了// 只有第一次会创建对象后续都是获取// 双判断的方式可以有效减少获取单例的加锁成本而且保证线程安全if (nullptr _instance) // 保证第二次之后所有线程不用在加锁直接返回_instance单例对象{LockGuard lockguard(_lock);if (nullptr _instance){_instance new ThreadPoolT();_instance-InitThreadPool();_instance-Start();LOG(DEBUG, 创建线程池单例);return _instance;}}LOG(DEBUG, 获取线程池单例);return _instance;}
双判断的方式为什么能减少单例的加锁成本呢
我们主要解决的是害怕多线程创建不止一个单例我们的目的是让该单例模式只生产一个单例围绕这一个核心去解决问题
同时有很多进程过来的时候都会去尝试加锁但是只有一个线程可以加锁成功然后会执行new操作这时候_instance nullptr就不成立了再后来的线程不会等待在锁上了直接判断外层的if就会退出了不然所有的线程都要等待锁了。 单判断为什么会出错
同时可能多个线程通过if判断等待锁第一个线程加锁完成之后执行创建退出之后其他线程可以继续抢锁抢到以后继续创建就保证不了线程安全
单例模式的注意点
单例模式下的构造函数必须要有但必须是私有的。赋值和拷贝函数禁用因为只创建1个单例 在类里面创建的静态变量在类内定义需要在类外初始化
4.代码和执行效果
代码
#pragma once#include iostream
#include vector
#include queue
#include pthread.h
#include Log.hpp
#include Thread.hpp
#include LockGuard.hppusing namespace ThreadModule;const static int gdefaultthreadnum 5;template typename T
class ThreadPool
{
public:static ThreadPoolT *GetInstance(){// 如果是多线程获取线程池对象下面的代码就有问题了// 只有第一次会创建对象后续都是获取// 双判断的方式可以有效减少获取单例的加锁成本而且保证线程安全if (nullptr _instance) // 保证第二次之后所有线程不用在加锁直接返回_instance单例对象{LockGuard lockguard(_lock);if (nullptr _instance){_instance new ThreadPoolT();_instance-InitThreadPool();_instance-Start();LOG(DEBUG, 创建线程池单例);return _instance;}}LOG(DEBUG, 获取线程池单例);return _instance;}void Stop(){LockQueue();_isrunning false;ThreadWakeup();UnlockQueue();}void Wait(){for(auto thread : _threads){thread.Join();LOG(INFO, %s is quit..., thread.name().c_str());}}bool Enqueue(const T t){bool ret false;LockQueue();if(_isrunning){_task_queue.push(t);if(_waitnum 0){ThreadWakeup();}LOG(DEBUG, enqueue task success);ret true;}UnlockQueue();return ret;}~ThreadPool(){pthread_mutex_destroy(_mutex);pthread_cond_destroy(_cond);}private:void LockQueue(){pthread_mutex_lock(_mutex);}void UnlockQueue(){pthread_mutex_unlock(_mutex);}void ThreadSleep(){pthread_mutex_unlock(_mutex);}void ThreadWakeup(){// 唤醒一个等待特定条件变量的线程pthread_cond_signal(_cond);}void ThreadWakeupAll(){// 唤醒所有等待特定条件变量的线程pthread_cond_broadcast(_cond);}// 单例模式下的构造函数必须要有但必须是私有的ThreadPool(int threadnum gdefaultthreadnum) : _threadnum(threadnum), _waitnum(0), _isrunning(false){pthread_mutex_init(_mutex, nullptr);pthread_cond_init(_cond, nullptr);LOG(INFO, ThreadPool Construct());}// 赋值和拷贝函数禁用因为只创建1个单例ThreadPoolT operator(const ThreadPoolT ) delete;ThreadPool(const ThreadPoolT ) delete;void Start(){for (auto thread : _threads){thread.Start();}}void InitThreadPool(){//构建出所有的线程并不启动for(int num 0; num _threadnum; num){std::string name thread std::to_string(num1);//bind函数到底有什么作用_threads.emplace_back(std::bind(ThreadPool::HandlerTask, this, std::placeholders::_1), name);LOG(INFO, init thread %s done, name.c_str());}}void HandlerTask(std::string name){LOG(INFO, %s is running..., name.c_str());//线程需要死循环去处理任务while(true){//1、保证队列安全LockQueue();//2、队列中不一定有数据while(_task_queue.empty() _isrunning){_waitnum;ThreadSleep();_waitnum--;}//2.1 如果线程池已经退出了任务队列是空的if(_task_queue.empty() !_isrunning){UnlockQueue();break;}// 2.2 如果线程池不退出 任务队列不是空的// 2.3 如果线程池已经退出 任务队列不是空的 --- 处理完所有的任务然后在退出// 3. 一定有任务, 处理任务T t _task_queue.front();_task_queue.pop();UnlockQueue();LOG(DEBUG, %s get a task, name.c_str());//4.处理任务这个任务属于线程独占的任务//t();LOG(DEBUG, %s handler a task, result is: %s, name.c_str(), t.ResultToString().c_str());}}int _threadnum;std::vectorThread _threads;std::queueT _task_queue;pthread_mutex_t _mutex;pthread_cond_t _cond;int _waitnum;bool _isrunning;// 添加单例模式static ThreadPoolT *_instance;static pthread_mutex_t _lock;
};// 在类里面创建的静态变量在类内定义需要在类外初始化
template typename T
ThreadPoolT *ThreadPoolT::_instance nullptr;template typename T
pthread_mutex_t ThreadPoolT::_lock PTHREAD_MUTEX_INITIALIZER;// 为什么双重判断就可以解决线程安全的问题
// 为什么static就可以不用创建对象直接调用函数呢执行结果