当前位置: 首页 > news >正文

线程池单例模式

线程池的概念                                                              

线程池是一种线程使用模式。 

一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。

  • 这避免了在处理短时间任务时创建与销毁线程的代价。
  • 线程池不仅能够保证内核的充分利用,还能防止过分调度。

tips:可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。

线程池的应用场景

  • 需要大量的线程来完成任务,且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务,使用线程池技术是非常合适的。因为单个任务小,而任务数量巨大,你可以想象一个热门网站的点击次数。 但对于长时间的任务,比如一个 Telnet连接请求,线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。
  • 对性能要求苛刻的应用,比如要求服务器迅速响应客户请求。
  • 接受突发性的大量请求,但不至于使服务器因此产生大量线程的应用。突发性大量客户请求,在没有线程池情况下,将产生大量线程,虽然理论上大部分操作系统线程数目最大值不是问题,短时间内产生大量线程可能使内存到达极限,出现错误。

线程池的实现

下面我们实现一个简单的线程池,线程池中提供了一个任务队列,以及若干个线程(多线程)。

  • 线程池中的多个线程负责从任务队列当中拿任务,并将拿到的任务进行处理。
  • 线程池对外提供一个Push接口,用于让外部线程能够将任务Push到任务队列当中。

线程池代码如下:

#include <iostream>
#include <pthread.h>
#include <vector>
#include <queue>
#include <string>static const int defaultnum = 5;struct ThreadInfo
{pthread_t tids;std::string NameThread;
};template<class T>
class ThreadPool
{
public:void Lock(){pthread_mutex_lock(&lock);}void UnLock(){pthread_mutex_unlock(&lock);}void Wait(){pthread_cond_wait(&cond, &lock);}void WakeUp(){pthread_cond_signal(&cond);}bool IsEmptyTask(){return _TasksQueue.size() == 0;}public:ThreadPool(int num = defaultnum): _threads(num){pthread_mutex_init(&lock, nullptr);pthread_cond_init(&cond, nullptr);}void ThreadStart(){int n = _threads.size();for(int i = 0; i < n; i++){pthread_create(&_threads[i].tids, nullptr, ThreadTasks, this);_threads[i].NameThread = "thread-" + std::to_string(i);}}std::string GetThreadName(pthread_t tid){for(auto& e : _threads){if(tid == e.tids){return e.NameThread; }}return "none";}static void *ThreadTasks(void* args){ThreadPool<T>* TP = static_cast<ThreadPool<T>*>(args);while(true){std::string Name = TP->GetThreadName(pthread_self());TP->Lock();while(TP->IsEmptyTask()){TP->Wait();}T t = TP->pop();TP->UnLock();t();std::cout << Name.c_str() << ' ' << std::endl;t.GetTask();}}T pop(){T t = _TasksQueue.front();_TasksQueue.pop();return t;}void push(const T &task){Lock();_TasksQueue.push(task);WakeUp();UnLock();}~ThreadPool(){pthread_mutex_destroy(&lock);pthread_cond_destroy(&cond);}
private:std::vector<ThreadInfo> _threads;std::queue<T> _TasksQueue;pthread_mutex_t lock;pthread_cond_t cond;
};

为什么线程池中需要有互斥锁和条件变量?

使用互斥锁的原因:STL容器一开始被设计时,就是为了追求效率,并没有考虑线程安全,多线程场景,pop数据时可能产生并发问题

使用条件变量的原因:

为什么线程池中的线程执行例程需要设置为静态方法?

Routine作为类的成员函数,该函数的第一个参数是隐藏的this指针,因此这里的Routine函数,虽然看起来只有一个参数,而实际上它有两个参数,此时直接将该Routine函数作为创建线程时的执行例程是不行的,无法通过编译。

静态成员函数属于类,而不属于某个对象,也就是说静态成员函数是没有隐藏的this指针的,因此我们需要将Routine设置为静态方法,此时Routine函数才真正只有一个参数类型为void*的参数。

但是在静态成员函数内部无法调用非静态成员函数,而我们需要在Routine函数当中调用该类的某些非静态成员函数,比如Pop。因此我们需要在创建线程时,向Routine函数传入的当前对象的this指针,此时我们就能够通过该this指针在Routine函数内部调用非静态成员函数了。

任务类型的设计

#pragma once#include "ThreadPool.hpp"enum
{EXITCODE = 0,DIVZERO,MODZERO
};class Task
{
public:Task(int x, int y, char oper, int exitcode_ = EXITCODE) : _data1(x), _data2(y), _oper(oper), exitcode(exitcode_){}void run(){switch (_oper){case '+':result = _data1 + _data2;break;case '-':result = _data1 - _data2;break;case '*':result = _data1 * _data2;break;case '/':if(_data1 == 0 | _data2 == 0){exitcode = DIVZERO;}else{result = _data1 / _data2;}break;case '%':if(_data1 == 0 | _data2 == 0){exitcode = MODZERO;}else{result = _data1 % _data2;}break;default:std::cout << "Symbol mismatch!" << std::endl;break;}SolveTask();}std::string _To_String(){std::string str;str += "[exitcode: ";str += std::to_string(exitcode);str += "]";str += " ";str += std::to_string(_data1);str += " ";str += _oper;str += " ";str += std::to_string(_data2);return str;}void GetTask(){std::cout << _data1 << " " << _oper << " " << _data2 << " = ?" << std::endl;}void SolveTask(){std::cout << _To_String() << " = " << result << std::endl;}void operator()(){run();}~Task(){}private:int _data1;int _data2;char _oper;int exitcode;int result;
};

主线程逻辑

主线程就负责不断向任务队列当中Push任务就行了,此后线程池当中的线程会从任务队列当中获取到这些任务并进行处理。

#include "SingletonThreadPool.hpp"
#include "Task.hpp"
#include <unistd.h>std::string oper = "+-*/%";int main()
{srand(time(nullptr));SingletonThreadPool<Task>* STP = new SingletonThreadPool<Task>(5);STP->ThreadStart();int len = oper.size(); while(true){sleep(1);int data1 = rand() % 10;int data2 = rand() % 10 + 1;char op = oper[rand() % len];Task t(data1, data2, op);t.push(t);t.GetTask();}return 0;
}

运行代码后一瞬间就有六个线程,其中一个是主线程,另外五个是线程池内处理任务的线程。

注意: 此后我们如果想让线程池处理其他不同的任务请求时,我们只需要提供一个任务类,在该任务类当中提供对应的任务处理方法就行了。

线程安全的单例模式

什么是单例模式

单例模式是一种 "经典的, 常用的, 常考的" 设计模式.

什么是设计模式

IT行业这么火, 涌入的人很多. 俗话说林子大了啥鸟都有. 大佬和菜鸡们两极分化的越来越严重. 为了让菜鸡们不太拖大佬的后腿, 于是大佬们针对一些经典的常见的场景, 给定了一些对应的解决方案, 这个就是 设计模式

单例模式的特点

某些类, 只应该具有一个对象(实例), 就称之为单例. 在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中. 此时往往要用一个单例的类来管理这些数据.

饿汉实现方式和懒汉实现方式

饿汉方式就是直接在类中将需要使用的对象先申请好

懒汉方式最核心的思想是 "延时加载". 从而能够优化服务器的启动速度.

例如:

#pragma onceclass Singleton
{
public:Singleton(){std::cout << "对象创建成功" << std::endl;}static Singleton& GetInstance(){return num;}private:static Singleton num;
};
#include <iostream>using namespace std;
#include "singleton.hpp"//static int num = 10;Singleton init1;
Singleton init2;
Singleton init3;
Singleton init4;
Singleton init5;
Singleton init6;int main()
{printf("启动!!!\n");    return 0;
}

可以看到饿汉式的单例模式会在程序启动之前对象就创建好了

饿汉方式实现单例模式

template <typename T> class Singleton 
{static T data;
public:static T* GetInstance() {return &data;} 
};

只要通过 Singleton 这个包装类来使用 T 对象, 则一个进程中只有一个 T 对象的实例

懒汉方式实现单例模式

template <typename T> class Singleton
{static T* inst; 
public:static T* GetInstance() {if(inst == NULL) {inst = new T();}return inst;} 
};

存在一个严重的问题, 线程不安全.
第一次调用 GetInstance 的时候, 如果两个线程同时调用, 可能会创建出两份 T 对象的实例.

懒汉方式实现单例模式(线程安全版本)

// 懒汉模式, 线程安全 
template <typename T> 
class Singleton { volatile static T* inst;  // 需要设置 volatile 关键字, 否则可能被编译器优化. static std::mutex lock; 
public: static T* GetInstance(){ if(inst == NULL)  //避免占用CPU和操作系统资源,做无意义的事,提高效率{  lock.lock();          // 使用互斥锁, 保证多线程情况下也只调用一次 new.if (inst == NULL) { inst = new T(); } lock.unlock(); } return inst; } 
}; 

注意事项:

1. 加锁解锁的位置

2. 双重 if 判定, 避免不必要的锁竞争

3. volatile关键字防止过度优化

将上述线程池代码改为单例模式

#pragma once#include <iostream>
#include <pthread.h>
#include <queue>
#include <vector>
#include <string>static const int defaultnum = 5;class ThreadInfo
{
public:pthread_t tid;std::string threadname;
};template <class T>
class SingletonThreadPool
{void Lock(){pthread_mutex_lock(&lock);}void UnLock(){pthread_mutex_unlock(&lock);}void Wait(){pthread_cond_wait(&cond, &lock);}void WakeUp(){pthread_cond_signal(&cond);}bool IsEmptyThreadPool(){return _tasksqueue.size() == 0;}public:static void* RoutineTasks(void* args){SingletonThreadPool<T> *TP = static_cast<SingletonThreadPool<T>*>(args);while(true){std::string name = TP->GetThreadName(pthread_self());TP->Lock();if(TP->IsEmptyThreadPool()){TP->Wait();}T t = TP->pop();TP->UnLock();t();std::cout << name << ' ' << std::endl;t.GetTask();}}public:void ThreadStart(){int num = _threads.size();for(int i = 0; i < num; i++){pthread_create(&_threads[i].tid, nullptr, RoutineTasks, this);_threads[i].threadname = "thread-" + std::to_string(i);}}T pop(){T task = _tasksqueue.front();_tasksqueue.pop();return task;}std::string GetThreadName(pthread_t tid){for(const auto& e : _threads){if(tid == e.tid)return e.threadname;}return "none";}void push(const T& task){Lock();_tasksqueue.push(task);WakeUp();UnLock();}static SingletonThreadPool<T>* GetInStance(){//避免占用CPU和操作系统资源,做无意义的事,提高效率if(inst == nullptr){//避免多线程模式下,同时申请多个instpthread_mutex_lock(&slock);if(inst == nullptr){inst = new SingletonThreadPool<T>;}pthread_mutex_unlock(&slock);}return inst;}private:SingletonThreadPool(int num = defaultnum):_threads(num){pthread_mutex_init(&lock, nullptr);pthread_cond_init(&cond, nullptr);}~SingletonThreadPool(){pthread_mutex_destroy(&lock);pthread_cond_destroy(&cond);}//防拷贝SingletonThreadPool(const SingletonThreadPool<T>& STP) = delete;SingletonThreadPool<T>& operator=(const SingletonThreadPool<T>& STP) = delete;private:std::vector<ThreadInfo> _threads;std::queue<T> _tasksqueue;pthread_mutex_t lock;pthread_cond_t cond;static SingletonThreadPool<T> *inst;static pthread_mutex_t slock;
}; template<class T>
SingletonThreadPool<T>* SingletonThreadPool<T>::inst = nullptr;template<class T>
pthread_mutex_t SingletonThreadPool<T>::slock = PTHREAD_MUTEX_INITIALIZER;
#include "SingletonThreadPool.hpp"
#include "Task.hpp"
#include <unistd.h>std::string oper = "+-*/%";int main()
{srand(time(nullptr));SingletonThreadPool<Task>::GetInStance()->ThreadStart();int len = oper.size(); while(true){sleep(1);int data1 = rand() % 10;int data2 = rand() % 10 + 1;char op = oper[rand() % len];Task t(data1, data2, op);SingletonThreadPool<Task>::GetInStance()->push(t);t.GetTask();}return 0;
}

相关文章:

  • 【设计模式区别】装饰器模式和适配器模式区别
  • 单例设计模式之懒汉式以及线程安全问题
  • 从循环角度分析逐位分离法
  • 【人工智能之大模型】详述大模型中流水线并行(Pipeline Parallelism)的​GPipe推理框架?
  • 如何选择合适的探针台
  • C#中wpf程序中的x名空间详解
  • 微信小程序 template 模版详解
  • 机器学习之二:指导式学习
  • 精益数据分析(27/126):剖析用户价值与商业模式拼图
  • 有源晶振与无源晶振详解:区别、应用与选型指南
  • 电子电器架构 --- 乘用车电气/电子架构开发的关键挑战与应对策略
  • SQL 查询进阶:WHERE 子句与连接查询详解
  • 【高频考点精讲】前端职业发展:如何规划前端工程师的成长路径?
  • PCL绘制点云+法线
  • 【教程】Windows通过网线共享网络给其它设备
  • python调用ffmpeg对截取视频片段,可批量处理
  • 介绍常用的退烧与消炎药
  • 前端学习笔记(四)自定义组件控制自己的css
  • 写了一个关于SpringAop记录用户操作的功能
  • 从入门到精通汇编语言 第七章(高级汇编语言技术)
  • 洛阳原副市长收礼品消费卡,河南通报6起违反八项规定典型问题
  • 同款瑞幸咖啡竟差了6元,开了会员仍比别人贵!客服回应
  • 伊朗外长:美伊谈判进展良好,讨论了很多技术细节
  • 体育公益之约跨越山海,雪域高原果洛孕育足球梦
  • 中国与肯尼亚签署共同发展经济伙伴关系框架协定
  • 今年五一,贵州一脸“爆相”