死锁:多个进程/线程对锁资源进行争抢访问,但是因为推进顺序不当,而造成互相等待,导致程序流程
无法继续推进,造成死锁;
死锁产生的四个必要条件:
1.互斥条件
2.不可剥夺条件
3.请求与保持条件
4.环路等待条件
死锁的预防:破坏死锁产生的必要条件
死锁的避免:银行家算法
读写锁与自旋锁
读写锁:
应用于: 写互斥,读共享
实现:
两个计数器:读者计数/写者计数
加写锁:对两个计数器进行判断 若有任意一个计数器>0;都无法加写锁需要等待
加读锁:对写者计数进行判断 若>0;都无法加读锁需要等待
读写锁是通过自旋锁实现的:不满足条件自旋等待
等待中不停循环对条件进行判断—自旋等待(cpu消耗较高,响应比较实时)
自旋锁的使用场景:对数据的操作时间确定很短的情况
对于挂起等待被唤醒的时间相较于数据处理时间可以忽略不记的情况下—这时更倾向于挂起
为了避免读写锁造成读者饥饿或者写者饥饿的情况:读写锁属性中可以设置(写者优先/读者优先)
读写锁默认读者优先
啥时候用信号量/条件变量
信号量(计数的场景)
条件变量(可以用于任意条件 任意场景)
线程池:大量的线程+任务队列 (基于生产者消费者模型实现的)
t1(线程创建)+t2(任务处理)+t3(线程销毁)=t(任务处理总时间)
在一个任务处理的总时间中,若1 +3所用的时间占用大量的比例;意味着大量任务的处理中,资源被浪费在线程的创建与销毁中,
这是不合理的
线程池的两个作用:
因此产生的线程池,创建大量的线程(并不退出),而是不断的将任务交给这些线程进行处理;
避免了大量线程创建/销毁带来的时间成本;
线程池中,线程数量是有最大上限的;避免出现峰值压力,瞬间资源耗尽导致再程序崩溃的危险
线程池的实现:
class Task{
int data;
void(*handler_t )(int data );
Run()
handler_t(data)
}
STL容器都是非线程安全的—使用的时候需要注意
线程安全的单例模式
单例模式:一种典型的常见设计模式
描述的场景:一个资源只能被加载分配一次—一个类只能被加载分配一次—一个类只能实例化一个对象
实现:
饿汉:资源一次性加载分配 —对象在程序初始化时实例化完毕
线程安全/程序运行起来比较快流畅/ 启动加载时间过长
class tmp{static T data; Tget_instance { return &data}} T tmp::data=(静态成员在类外初始化)
懒汉:资源使用的时候再加载分配 —对象在使用的时候再去实例化(加载快 资源的利用率比较高 同一时间消耗的资源少 程序运行中没有饿汉流畅)
class tmp {static Tdata;Tget_instance(){data=new T();return data;}
volatil(保持内存可见性,防止编译器优化) static T *data;
lock(); if ()dataNULL )data=newT();unlock();
if (dataNULL){loock();if(data==NULL)data=new T();unlock();}