店铺网站平台建设方案,深圳公司电话,wordpress加分页,个人网页设计手绘文章目录线程池线程池原理代码示例单例模式饿汉模式懒汉模式饿汉懒汉对比其他的锁线程池
线程池原理
线程池是一种线程使用模式。在多线程应用中#xff0c;若每有一个任务#xff0c;线程就去调度相应的函数去创建#xff0c;当任务过多时#xff0c;每次都去调度且每…
文章目录线程池线程池原理代码示例单例模式饿汉模式懒汉模式饿汉懒汉对比其他的锁线程池
线程池原理
线程池是一种线程使用模式。在多线程应用中若每有一个任务线程就去调度相应的函数去创建当任务过多时每次都去调度且每次用完销毁影响效率加重CPU的负载
而线程池是提前创建好的一批线程不固定长度没任务时就挂起等待有任务分配时就被唤醒等待分配任务但也要具体分场景例如任务时间短且任务量大的时候
线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量 应用场景 需要大量的线程来完成任务且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务使用线程池技术是非常合适的。因为单个任务小而任务数量巨大你可以想象一个热门网站的点击次数。 但对于长时间的任务比如一个Telnet连接请求线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了 对性能要求苛刻的应用比如要求服务器迅速响应客户请求 接受突发性的大量请求但不至于使服务器因此产生大量线程的应用 突发性大量客户请求在没 有线程池情况下将产生大量线程虽然理论上大部分操作系统线程数目最大值不是问题短时间内产生大量线程 可能使内存到达极限出现错误
代码示例
创建一个任务队列和线程池外部向任务队列Push任务任务队列唤醒线程池并Pop给线程池线程池分配出线程去执行任务
/*****************************************任务队列*************************************************/
#pragma once
#include string
#include queue
#include iostream
#include pthread.h
#include unistd.h
#include ctime
#include cstdlib
#include Task.hppnamespace dd
{const int Num 5;template class Tclass ThreadPool{
public:ThreadPool(int num Num) : _num(num){pthread_mutex_init(_mtx, nullptr);pthread_cond_init(_cond, nullptr);}~ThreadPool() {pthread_mutex_destroy(_mtx);pthread_cond_destroy(_cond);}//加锁void Lock(){pthread_mutex_lock(_mtx);}//解锁void Unlock(){pthread_mutex_unlock(_mtx);}//判断空bool isEmpty(){return _task_queue.empty();}//唤醒条件变量的等待队列void WakeUp(){pthread_cond_signal(_cond);} //条件变量挂起等待void wait(){pthread_cond_wait(_cond,_mtx);}//创建线程池void InitThreadPool(){pthread_t tid;for (int i 0; i num_; i){pthread_create(tid, nullptr, Rountine, (void*)this);}}// 在类内要让线程执行类内成员函数是不可行的// 普通成员函数中参数默认一个this指针// 需要调用静态方法static void* Rountine(void* args){//static函数不能访问类内成员和函数所以让外面实例化的对象调用Init通过this传进来ThreadPoolT* tp (ThreadPoolT*)args;pthread_detach(pthread_self());while (true){// 从任务队列中拿任务 为了能拿任务队列里的东西需要传递this指针tp-Lock();while (tp-isEmpty()) {tp-wait();}//拿任务T t;tp-Poptask(t);tp-Unlock();//拿完任务任务就属于调用Pop的那个线程了不是临界资源了//执行任务执行任务时就不用占着锁了t.Run();}} //放任务放任务时访问的是临界资源任务队列所以要上锁void Pushtask(const T in){Lock();task_queue_.push(in);Unlock();WakeUp();}//拿任务拿任务时的上下文是有锁的所以不需要在这里上锁void Poptask(T* out){*out task_queue_.front();task_queue_.pop();} private:int _num;// 线程池中的线程数量std::queueT _task_queue;pthread_mutex_t _mtx;pthread_cond_t _cond;};
}/**********************************************************************************************/
#pragma once
#include iostream
#include pthread.h
namespace dd
{class Task
{
public:Task(){}Task(int x,int y,char op):_x(x),_y(y),_op(op){}int Run(){int ret 0;switch(_op){case :ret _x _y;break;case -:ret _x - _y;break;case *:ret _x * _y;break;case /:ret _x / _y;break;case %:ret _x % _y;break;default:break;}//std::cout _x _op _y ret std::endl;std::cout pthread_self() : _x _op _y ret std::endl;}
private:int _x;int _y;char _op;};}/**********************************************************************************************/
#include thread_pool.hpp
#include Task.hpp
using namespace dd;
int main()
{ThreadPoolTask* tp new ThreadPoolTask();//创建线程tp-InitThreadPool();srand((unsigned int)time(nullptr));while (true){// 主线程push任务 真实情况中 任务一般是从网络来的Task t(rand() % 20 1, rand() % 10 1, -*/%[rand() % 5]);tp-pushtask(t);// sleep(1);}return 0;
}
单例模式
单例模式是指在整个系统生命周期内保证在内存中一个类只会创建且仅创建一次对象的设计模式确保该类的唯一性 可以节省内存节约资源对于一般频繁创建和销毁对象的可以使用单例模式 可以避免对资源的多重占用 例如在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据也就是这么大的数据在内存只需要有一份避免数据冗余例如动态库 单例类的特点 构造函数和析构函数为私有类型目的是禁止外部构造和析构拷贝构造函数和赋值构造函数是私有类型目的是禁止外部拷贝和赋值确保实例的唯一性类中的成员变量是静态的静态的无论实例化多少对象都只有公用一个静态成员变量类中要有一个获取实例的静态方法可以全局访问 单例模式可以分为两种模式饿汉模式、懒汉模式
饿汉模式
注意几点细节 需要将构造函数拷贝构造函数赋值运行符重载函数屏蔽防止在外部实例化对象 在类中创建一个静态类对象该静态类对象会在程序运行时创建需要在类外初始化 类中写一个接口静态返回静态类对象的地址。因为无法调用拷贝构造不能直接返回类对象
template typename T
class Singleton
{static T data;//static SingletonT dataSingelTon() delete;SingelTon(const SingelTon st) delete;;SingelTon operator(const SingelTon st) delete;public:static T* GetInstance() {return data;}
};
templateclass T
T Singleton::data /**/;
//SingletonT Singleton::data /**/; 吃完饭, 立刻洗碗, 这种就是饿汉方式. 因为下一顿吃的时候可以立刻拿着碗就能吃饭也就是系统一运行就初始化创建实例当需要时直接调用即可。这种方式本身就可以保证线程安全没有多线程的线程安全问题
懒汉模式
懒汉模式注意的细节
需要将构造函数拷贝构造函数赋值运行符重载函数屏蔽防止在外部实例化对象。在类中成员包含一个类对象指针而不是对象。类中写一个接口静态用户返回创建后的对象但只能调用一次因为是需要时才会创建所以需要考虑线程安全问题保证多线程下只能创建一个对象
template class T
class Singleton
{volatile static T* inst; // 需要设置 volatile 关键字, 否则可能被编译器优化.static std::mutex lock;Singleton(){pthread_mutex_init(lock,nullptr);}~Singleton(){pthread_mutex_destroy(lock);}Singleton(const SingletonT sl) delete;SingletonT operator(SingletonT sl) delete;public:static T* GetInstance() {if (inst NULL) { // 双重判定空指针, 降低争锁冲突, 提高性能.lock.lock(); // 使用互斥锁, 保证多线程情况下也只调用一次 new.if (inst NULL) {inst new T();}lock.unlock();}return inst;}
};
//类外初始化静态成员
template class T
T* Singleton::inst nullptr; 吃完饭, 先把碗放下, 然后下一顿饭用到这个碗了再洗碗, 就是懒汉方式也就是系统运行中实例并不存在只有当需要使用该实例时才会去创建并使用实例。这种方式要考虑线程安全问题
饿汉懒汉对比
饿汉模式 优点 实现简单不需要考虑线程安全问题。 缺点 启动慢。如果实例的对象占用资源很多在启动时需要加载。 如果多个单例类对象在程序启动时实例对象的顺序不确定。如果对象之间有依赖关系就麻烦了。 懒汉模式 优点 启动快。在需要时才会实例化对象加载资源。 多个单例类对象实例化的顺序可以确定。取决于调用类的函数的顺序。 缺点 实现复杂。需要考虑线程安全问题。 其他的锁
悲观锁在每次取数据时总是担心数据会被其他线程修改所以会在取数据前先加锁读锁写锁行 锁等当其他线程想要访问数据时被阻塞挂起。乐观锁每次取数据时候总是乐观的认为数据不会被其他线程修改因此不上锁。但是在更新数据前 会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式版本号机制和CAS操作。CAS操作当需要更新数据时判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等 则失败失败则重试一般是一个自旋的过程即不断重试。