目录
一、单列模式
1.1 单列模式概念以及实现条件
1.2 饿汉模式
1.1.1 饿汉模式代码实现
1.1.2 饿汉模式特征和优缺点
1.3 懒汉模式
1.3.1 懒汉模式代码实现
1.3.2 懒汉模式特征以及优缺点
二、线程池
2.1 线程池概念
2.2 实现简单线程池逻辑
2.3 模拟实现懒汉模式线程池
2.3.1 mutex.hpp 封装锁
2.3.2 Task.hpp 任务封装
💡💡2.3.3 Thread.hpp 线程封装
2.3.4 ThreadPool.hpp 线程池封装
2.3.5 main.cc 上层代码
2.3.6 执行结果展示
2.4 线程池应用场景
一、单列模式
1.1 单列模式概念以及实现条件
单例模式主要确保一个类只有一个实例!也就是对象唯一。其中饿汉、懒汉模式是单列模式的一种。
使对象唯一的条件:
- 私有的构造函数:单例类应当拥有一个私有的构造函数,以防止外部代码创建该类的实例。
- 公有的静态方法:单例类应当提供一个公有的静态方法,以供外部代码获取该类的唯一实例。
- 私有的静态变量:单例类应当拥有一个私有的静态变量,用于存储该类的唯一实例。
- 线程安全:在多线程环境下,应当保证单例类的唯一实例的创建和获取都是线程安全的。
1.2 饿汉模式
1.1.1 饿汉模式代码实现
class Singleton {
public:
//静态成员函数--》不需要this指针
static Singleton& getInstance() {
static Singleton instance; //静态成员对象--》生命周期长
return instance;
}
// 防止拷贝构造函数和赋值运算符
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
private:
Singleton() {} //构造函数私有化
};
1.1.2 饿汉模式特征和优缺点
特征:
- 在类加载时就创建唯一实例,保证了一个类只有一个实例。
- 实现起来比较简单,没有多线程同步问题。
优点:
- 由于实例在类加载时就被创建,因此不会造成资源的浪费。
- 没有多线程同步问题,保证了线程安全。
缺点:
- 如果该类从未被使用,那么它的实例对象就会被创建并一直占用内存,即使该实例对象可能永远不会被使用。
1.3 懒汉模式
1.3.1 懒汉模式代码实现
#include <mutex>
//懒汉模式 main函数之后创建对象
class InfoSingleton
{
public:
static InfoSingleton& GetInstance()
{
//双重检查 最外面if可以保证不会每次都加锁判断
if (_psins == nullptr)
{
LockGuard<mutex> lock(_smtx);
if (_psins == nullptr)
{
_psins = new InfoSingleton;
}
}
return *_psins;
}
private:
InfoSingleton() {}
//封死拷贝赋值构造
InfoSingleton(const InfoSingleton& s) = delete;
InfoSingleton& operator=(const InfoSingleton& s) = delete;
static InfoSingleton* _psins;
static mutex _smtx;
};
InfoSingleton* InfoSingleton::_psins = nullptr;
mutex InfoSingleton::_smtx;
1.3.2 懒汉模式特征以及优缺点
特征:
懒汉模式是一种单例模式,它的特点是在第一次使用实例对象时,才创建对象。
优点:
- 在第一次使用实例对象时,创建对象进程启动无负载。也就是说,如果单例对象构造十分耗时或者占用很多资源,如加载插件、初始化网络连接、读取文件等等,而有可能该对象在程序运行时并不会被使用,那么在程序一开始就进行初始化会导致程序启动时非常缓慢,使用懒汉模式(延迟加载)则能避免这种情况。
缺点:
- 懒汉模式相对于饿汉模式来说,实现上更复杂一些,因为涉及到线程安全问题。
二、线程池
2.1 线程池概念
线程池是一种多线程处理形式,它预先将任务添加到队列中,然后在创建线程后自动启动这些任务。线程池中的线程都是后台线程,每个线程都使用默认的堆栈大小,以默认的优先级运行,并处于多线程单元中。
2.2 实现简单线程池逻辑
线程池的工作原理如下:
- 线程池的初始化:在创建线程池后,线程池会根据配置初始化一定数量的核心线程,等待任务来临。--》单列模式实现线程池,提高创建线程的效率,有任务来立刻有执行线程!
- 线程的管理:线程池会管理线程的生命周期。当一个线程完成任务后,它会回到线程池中等待下一个任务--》利用一个数组来组织管理线程
- 任务的提交:线程池提供了接口供外部提交任务,这些任务会被封装为一个个的工作单元并加入到线程池的工作队列中。--》任务队列 Push接口放任务到队列,同步信号给线程
- 任务的执行:线程池中的线程会循环从工作队列中取出任务并执行。--》线程池Run->一个个线程Run互斥取任务
- 线程池的关闭:当不再需要线程池时,可以通过调用关闭方法来停止线程池。这将会停止所有正在执行的任务,销毁所有的核心线程,并释放线程池相关的资源。
2.3 模拟实现懒汉模式线程池
2.3.1 mutex.hpp 封装锁
RAII的思想,通过类生命周期来加锁解锁!
#include<iostream>
#include<pthread.h>
class Mutex
{
public:
Mutex(pthread_mutex_t* lock_p = nullptr):lock_p_(lock_p)
{}
void lock()
{
if(lock_p_)
pthread_mutex_lock(lock_p_);
}
void unlock()
{
if(lock_p_)
pthread_mutex_unlock(lock_p_);
}
private:
pthread_mutex_t* lock_p_;
};
class LockGuard
{
public:
LockGuard(pthread_mutex_t* mutex):mutex_(mutex)
{
mutex_.lock();
}
~LockGuard()
{
mutex_.unlock();
}
private:
Mutex mutex_;
};
2.3.2 Task.hpp 任务封装
#pragma once
#include<functional>
#include<iostream>
#include<unistd.h>
#include<string>
// 任务对象
class Task
{
public:
//==typedef
using func_t = std::function<double(int, int, char)>;
Task() {}
Task(func_t callback, int x = 0, int y = 0, char op = '+') : _x(x), _y(y), _op(op), _callback(callback)
{
}
// 仿函数
std::string operator()()
{
double ret = _callback(_x, _y, _op);
char buffer[64];
snprintf(buffer, sizeof buffer, "%d %c %d = %lf", _x, _op, _y, ret);
return buffer;
}
private:
int _x;
int _y;
char _op;
func_t _callback; // 回调函数
};
//处理数据函数
double calculator(int x, int y, char op)
{
double ret = 0.0;
switch (op)
{
case '+':
ret = x + y;
break;
case '-':
ret = x - y;
break;
case '*':
ret = x * y;
break;
case '/':
if (y == 0)
ret = 0;
else
ret = (double)x / y;
break;
case '%':
if (y == 0)
ret = 0;
else
ret = x % y;
break;
default:
break;
}
return ret;
}
💡💡2.3.3 Thread.hpp 线程封装
#pragma once
#include <iostream>
#include <string>
#include <pthread.h>
#include <functional>
#include <assert.h>
using func_t = std::function<void *(void *)>;
namespace MyThread
{
class Thread
{
public:
Thread(func_t callback, void *args = nullptr) : _callback(callback), _args(args)
{
char namebuffer[64];
snprintf(namebuffer, sizeof namebuffer, "thread %d ", ++threadNum);
_name = namebuffer;
}
void start()
{
int n = pthread_create(&_tid, nullptr, start_route, (void *)this);
assert(n == 0);
(void)n;
}
void join()
{
pthread_join(_tid, nullptr);
}
std::string ThreadName()
{
return _name;
}
void *callback()
{
return _callback(_args);
}
private:
//在类中调用线程执行流函数必须要static-->因为默认的函数是只有一个void*参数,如果没有static,会默认带有隐藏参数this!
//仅仅使用stati后,还要考虑如何访问类成员变量\函数-->传入的void*参数是this!
static void *start_route(void *args)
{
Thread *pt = static_cast<Thread *>(args);
return pt->callback();
}
private:
pthread_t _tid; //线程ID
void *_args; //线程所拥有的资源
std::string _name; //线程名称
func_t _callback; //回调函数
static int threadNum;//线程对象个数
};
int Thread::threadNum = 0;
}
2.3.4 ThreadPool.hpp 线程池封装
#pragma once
#include "Thread.hpp"
#include "mutex.hpp"
#include "Task.hpp"
#include <vector>
#include <queue>
#include <unistd.h>
#include <mutex>
using namespace MyThread;
static const int gnum = 5;
template <class T>
class ThreadPool
{
//和Thread.hpp中的理由一样 static防this指针 args=this
static void *Handeler(void *args)
{
while (true)
{
T t;
ThreadPool<T> *Self = static_cast<ThreadPool<T> *>(args);
{
LockGuard lock(&Self->_mutex);
while (Self->_TaskQueue.empty())
{
pthread_cond_wait(&Self->_cond, &Self->_mutex);
}
t = Self->_TaskQueue.front();
Self->_TaskQueue.pop();
}
std::cout << "线程[" << pthread_self() << "]处理完了一个任务: " << t() << std::endl;
}
return nullptr;
}
//构造函数私有 初始化创建一批线程
ThreadPool(const int num = gnum) : _num(num)
{
pthread_mutex_init(&_mutex, nullptr);
pthread_cond_init(&_cond, nullptr);
for (int i = 0; i < _num; i++)
{
_threads.push_back(new Thread(Handeler, this));
}
}
//拷贝赋值封死
ThreadPool(const ThreadPool<T> &) = delete;
void operator=(const ThreadPool<T> &) = delete;
public:
// 懒汉模式 延迟加载
static ThreadPool<T> *GetInstance()
{
if (_tp == nullptr)
{
_singleton_lock.lock();
if (_tp == nullptr)
_tp = new ThreadPool<T>();
_singleton_lock.unlock();
}
return _tp;
}
//执行线程
void run()
{
for (const auto &t : _threads)
{
t->start();
std::cout << t->ThreadName() << "starts..." << std::endl;
}
}
//放数据接口
void push(const T &in)
{
LockGuard lock(&_mutex);
_TaskQueue.push(in);
pthread_cond_signal(&_cond);
}
~ThreadPool()
{
pthread_mutex_destroy(&_mutex);
pthread_cond_destroy(&_cond);
for (const auto &t : _threads)
delete t;
}
private:
std::vector<Thread *> _threads; //数组管理线程
int _num;
std::queue<T> _TaskQueue; //任务队列
pthread_mutex_t _mutex;//互斥
pthread_cond_t _cond;// 同步
//单列模式
static ThreadPool<T> *_tp;
static std::mutex _singleton_lock;
};
template <class T>
ThreadPool<T> *ThreadPool<T>::_tp = nullptr;
template <class T>
std::mutex ThreadPool<T>::_singleton_lock;
2.3.5 main.cc 上层代码
#include "ThreadPool.hpp"
#include <memory>
#include <unistd.h>
int main()
{
srand((unsigned long)time(nullptr) ^ getpid());
//初始化并运行线程
ThreadPool<Task>::GetInstance()->run();
sleep(2);
while (true)
{
//1.获取任务
const char *str = "+-*/%";
int x = rand() % 10 + 1;
int y = rand() % 5 + 1;
char op = str[rand() % 5];
Task t(calculator, x, y, op);
//2.放入任务至队列中
ThreadPool<Task>::GetInstance()->push(t);
std::cout << "生产任务: " << x << " " << op << " " << y << " = ?" << std::endl;
sleep(1);
}
while (true)
{
sleep(1);
}
return 0;
}
2.3.6 执行结果展示
2.4 线程池应用场景
线程池主要应用在以下场景中:
- 处理CPU密集型任务:线程池可以预先创建一定数量的线程,避免在线程需要时创建线程,从而提高效率。这对于处理CPU密集型任务非常有用,例如进行大量计算或者处理某个特定任务。
- 面向用户的功能聚合:当业务开发中涉及到大量的并发调用时,线程池可以通过封装调用为任务并行执行的方式,提高整体响应时间。例如,如果一个系统有很多用户请求,线程池可以管理这些请求的并发执行,避免请求的阻塞。
此外,线程池在以下情况中也可以发挥出其优势:
- 任务执行时间短且数量大:如果需要执行大量的小任务,每个任务的处理时间都很短,此时使用线程池可以更有效地利用资源。
- 任务的执行顺序无关紧要:如果任务之间的执行顺序并不重要,那么线程池可以让任务异步执行,提高整体效率。
- 需要执行重复任务:如果需要执行重复的任务,使用线程池可以避免重复创建和销毁线程,提高性能和效率。
- 任务的间歇性执行:如果任务是间歇性地执行,使用线程池可以在需要时立即提供服务,避免空闲时间过长。
- 任务的响应时间要求高:如果要求任务的响应时间非常短,使用线程池可以更快地处理任务,提高用户体验。
- 需要限制并发数:如果需要限制并发执行的线程数量,线程池可以通过控制最大并发数来管理资源的使用。
总的来说,线程池主要应用在需要对并发执行的任务进行高效、有序、可控制管理的场景中。