在了解完多线程的绝大部分概念之后,我们本篇博客作为Linux多线程中的最后一篇博客,来对其中剩余内进行一个收尾。
目录
1.线程池
1.1引入
1.2原理
1.3优点
1.4实现
2.单例模式
2.1内容
2.2原理
2.3实现
2.3.1饿汉模式实现
2.3.2懒汉模式实现
1.线程池
1.1引入
在之前的线程学习当中,我们对于线程的使用都是即用即创建即销毁,在面对大量任务需要处理时,便会为了提高进程效率来创建线程完成任务,每个任务都会创建一个与之相应的想成功。
但是这样创建多个线程来处理多任务存在两个十分明显的缺点:当某一时刻瞬间产生大量任务(瞬间峰值压力),一次性创建线程过多会导致程序崩溃;当多个任务的处理时间较短,那么进程会将大部分时间浪费在线程的创建和销毁上。
1.2原理
于是我们引入线程池的概念,创建一个由线程组成的池子,当任务(数据)产生,我们便可以直接直接从线程池中分配一个线程来完成该项任务,任务处理完毕线程交回线程池。
但是实际中从线程池中取线程,完成任务到交回线程这三步骤是比较难以实现的。所以我实际中线程池的实现是通过固定数量的线程,和构建一个线程安全的任务队列。线程池中的线程便不断向任务队列中取出任务来完成,如果不存在需要完成的任务,则阻塞等待。
1.3优点
- 数据任务可以在任务队列中缓冲,并且数量可以控制,避免了峰值压力下资源耗尽的情况;
- 避免了数据任务简单时,线程频繁创建和销毁所带来的时间成本。
1.4实现
结合线程池的原理,我们需要:固定数量的线程和线程安全的任务队列,这两项必不可少的构成要素。
我们设计线程入口函数,来使用线程池中的线程不断的从任务队列中取数据进行处理。在设计中我们不关注不同任务的具体处理方法,因为我们将不同任务的不同处理方法写入到线程入口函数中,会造成程序耦合性过高,而且任务处理方法的改变也会设计到线程池中代码的修改。
所以我们不关注任务对应的具体处理方法,而是将其作为参数传递到线程中,让线程来使用该处理方法对任务数据进行错做即可。
总结:我们实现两个类:任务类和线程类。任务类中存在任务的具体内容和任务的处理方法;线程类中存在线程的创建……。
最后编译并执行得到结果如下:
值得注意的是,在本次代码执行中我们使用了一些C++11中的特性,因此编译过程中的执行语句应该如下(附makefile截图):
2.单例模式
2.1内容
接下来是我们线程应用中的最后一个知识点,叫做单例模式。单例模式也是线程应用中一种典型的设计模型,它的针对场景是:一个类只能实例化一个对象。
这样的设计存在两个出发点:从资源角度而言,资源在内存中只存在一份;从数据角度而言,如果类中只存在一个对象,则无论何时获取到的数据都是一致的。
2.2原理
单例模式的实现我们存在两种实现思想,一种叫做饿汉模式,一种叫做懒汉模式。
对于饿汉模式而言,它会将对象实例化完毕,申请资源完毕,便于可以随时使用资源,是一种以空间换时间的思想。这样做的好坏十分明显,好处是效率最大化,资源使用时可以随意使用;坏处是程序初始化速度慢,占用内存资源较多。
对于懒汉模式而言,它会等到对象需要使用的时候再实例化,资源需要使用时再进行申请,是一种延迟加载的思想。这样做的好坏也很明显,好处是占用内存资源少,程序初始化速度快;坏处是需要访问一个对象时需要去实例化对象加载资源,即第一次访问对象时间较长。
从线程安全的角度而言,饿汉模式并不涉及线程安全问题,因为所有的对象在一开始都会实例化完毕,不存在多个线程同时访问申请资源的情况。但是懒汉模式存在线程安全的问题,因为懒汉模式是使用资源时才实例化对象,如果存在多个线程同时访问同时实例化加载,便会导致线程安全问题的产生。
2.3实现
2.3.1饿汉模式实现
我们可以将对象设计为静态资源,这样便可以在程序初始化阶段完成对象的实例化。
最终编译并执行得到结果如下:
2.3.2懒汉模式实现
我们不直接定义对象,通过定义静态对象指针的方式等待访问时再实例化(new)。
最终编译并执行可得到结果如下:
值得一提的是,在定义_eton数据时,编译器会认为它的访问频率较高,将其直接加载到寄存器之中。这样优化代码之后,我们后续访问到的_eton都只会是空。所以我们可以加上volatile关键字进行修饰,保证内存可见性,防止编译器过度优化。