线程池
1.概念:
- 一种线程的使用模型,由于线程的过多,会带来过度的调度开销,对于线程的创建和线程的等待,会有大量的时间以及效率的丢失,这样会大大的折损效率,而线程池的使用可以解决这一问题,线程池维护着多个线程,通过去等待客户所分发的资源并且可以并发执行任务。这大大的避免了线程的创建以及等待的时间浪费的代价。 线程池不仅保证内核的充分利用,也能防止过分的调度。可用的线程数量根据情况而定。
①:一个任务处理的成本:
线程的创建时间+任务的处理时间+线程的等待时间 = 总耗时。
而如果任务处理的时间比较短,那么相较于线程创建和等待时间就较长,那么就大大浪费了时间。
②:风险:若线程无限制,那么在峰值的压力之下,总会有资源耗尽系统崩溃风险。
说白了:线程池,就是创建好一堆的进程,然后创建一个消息队列,这些进程等待着消息队列中的消息,有消息就处理,没有就阻塞等待。
2.线程池的应用场景:
- 需要大量线程完成任务,并且处理时间要短。
- 对性能要求高。
- 接收突发性的大量请求,但不至于让服务器因此产生大量的线程的应用。
3.线程池模拟实现如下:
其中:我们设置ThreadTask这个类的意义,就是让我们的线程不只是以一种方式去处理这个任务,因为对于线程池,他应该是拿到函数,并且拿到参数去处理的,不可能只设定一个函数去让他执行,如果这样,那么线程池就有点局限了。
单例模型
1.概念:就是对于一个类,他只能实例化出一个对象,提供一个访问接口。(一个资源在内存中只能有一份)
2.目的:
- 节省内存空间。
- 防止数据二义混淆。(多种数据在同一时刻的不同体现)
对于整个单例模型,它一共分为以下两种模型:
饿汉模型
1.概念:资源提前就全部加载初始化完,用的时候直接用。(意思就是,这个类我们提前直接初始化完成,就已经占用一个空间了,到时候,我们使用的时候,就不用再去等待其申请空间了一系列事情,直接用,节省了时间,但是在开始的时候就直接加载,如果我们用不到,那么就占用了内存)
实现方式:
class Singleton
{
public:
static T* GetInstance()
{
return &Data;
}
private:
static Singleton Data;//设置为静态成员,全局共享,并且在程序运行前初始化加载
Singleton()//构造函数设置为私有成员。
{}
};
- 构造函数私有化,无法在类外实例化出对象。
- 成员变量(资源数据)静态化,资源单独一份,运行前初始化,所以初始化过程不用考虑线程问题。
- 使用对象时,通过类的接口进行接收。
懒汉模型
1.概念:一开始不会初始化资源,等到我们使用的时候再初始化。(意思是,它不像饿汉模式,在程序运行开始之前就初始化对象,而懒汉模型,就是等你用的时候再去创建,这样不占内存,但是有可能会浪费时间)
但是:在懒汉模型中,要注重线程安全问题。
2.实现方式:
①:线程不安全方式:
class Singleton
{
public:
static T* GetInstance()
{
if (data == NULL)
{
data = new T();
}
return data;
}
private:
static Singleton* data;
Singleton()//构造函数设置为私有成员。
{}
};
重要的问题就是,当两个线程同时创建的这个类的时候,那么就会出现了两个对象,那么就违反了单例模式的规定了。
②:线程安全方式:
class Singleton()
{
private:
volatile static Singleton* data;
static std::mutex _mutex;
Singleton()//构造函数设置为私有成员。
{}
public:
volatile static Singleton* Getlnstance()
{
if(data == NULL)
{
_mutex.lock();
if(data == NULL)
{
data* = new Singlent();
}
_mutex.unlock();
}
return data;
}
};
其中:
- 构造函数私有化。
- 定义静态指针成员访问。(为了在用的时候申请加载)
- 在访问接口的时候加锁,保护资源初始化的安全。
- 在加锁之外二次加锁,提高运行效率。(防止当这个对象已经存在了,本来直接返回,中途还要遇到一次加锁解锁操作的时间浪费)
- 加volatile关键字,防止编译器过度优化,所以使用其修饰成员变量。(例如,第一次给data为NULL,那么编译器过度优化后,他会认为data一直都是NULL,那么就会每次都会创建一个对象,那么就出问题了)