1.线程安全
1.1什么是线程安全
就是多个线程在访问公共资源的时候,能够正确执行,不会相互干扰活破坏彼此的执行结果,一般而言,多个线程并发同一段只有全局变量的代码时,不会出现不同的结果,到那时对全局变量或者静态变量进行操作,并且没有琐保护的的情况下,很容易出现该问题;
1.2 什么是重入
重入就是同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们把这种情况成为重入;一个函数在重入的情况下,运行结果不会吃先任何不同或者问题,这个函数成为可重入函数,否则,是不可重入函数;
重入可分为两种:
①多线程重入函数 ②信号导致一个执行流重复进入函数
(1)可重入与线程安全联系
可重入函数一定是线程安全的
线程安全函数不一定是可重入的
可重入是线程安全的子集,要求更严格;
(2)可重入与线程安全区别
线程安全:强调多线程并发调用时安全,靠锁保证,允许使用全局变量;
可重入:强调重复进入执行时安全,不能使用全局 / 静态变量,不能加锁;
如果不考虑信号导致⼀个执行流重复进入函数这种重入情况,线程安全和重入在安全角度不做区分;但是线程安全侧重说明线程访问公共资源的安全情况,表现的是并发线程的特点;可重入描述的是⼀个函数是否能被重复进入,表示的是函数的特点
常见的可重入函数的特点:
①不使用全局变量或静态变量
②不使用用 malloc 或者 new 开辟出的空间
③不调用不可重入函数
④不返回静态或全局数据,所有数据都有函数的调用者提供
⑤使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据
1.3饿汉模式和懒汉模式

懒汉模式最大的特点就是"延时加载",而在我们的Linux中其实有很多都是这样的模式,比如缺页中断、写时拷贝、new/malloc对空间的申请,其实物理内存上还没有开辟,只有你真的要用的时候才会在物理内存上申请,这样的最大的好处就是资源利用率高;
而且在我们使用饿汉这样的模式的时候,我们发现它在开始就已经static T data->创建了对象,这样如果我们的对象的资源很大就很浪费时间,但是像懒汉模式,只是创建了一个指向该对象的指针,在我们真的使用的时候慢慢加载,很大程度上节约了时间,加速了我们启动程序的时间;
1.4 单例模式
单例模式是 C++ 等面向对象语言中最常用的设计模式之一,核心目标是保证一个类在整个程序运行期间,有且仅有一个实例对象,并提供一个全局唯一的访问入口(通常是getInstance()静态方法)。它通过私有化构造函数、禁用拷贝构造与赋值运算符,从语法层面彻底禁止外部手动创建或复制对象,确保实例的唯一性,避免多实例带来的资源竞争、逻辑混乱等问题,是全局统一管理线程池、日志系统等全局服务的标准方案。
单例模式的本质是全局资源的统一管理,它能避免重复创建对象造成的内存浪费,同时保证所有模块共享同一份实例状态,让全局服务的调用更简洁、逻辑更可控。它广泛应用于线程池、日志类、配置管理类等需要全局唯一实例的场景,核心优势是实例唯一、访问便捷、生命周期可控,是 C++ 后端开发、高性能并发编程中最基础也最重要的设计模式之一。
核心三要素(背会 = 掌握单例)
①构造函数私有化 → 别人不能 new
②禁用拷贝 + 赋值 → 别人不能复制
③静态 getInstance () → 全局唯一获取入口
cpp
#include <iostream>
using namespace std;
// 单例类
class Singleton
{
public:
// 🔴 全局唯一访问入口(最重要)
static Singleton* getInstance()
{
// 静态对象:程序生命周期只创建一次
static Singleton instance;
return &instance;
}
// 测试函数
void show()
{
cout << "我是唯一的单例对象" << endl;
}
private:
// 🔴 私有化构造函数:禁止外部创建对象
Singleton()
{
cout << "单例构造------只执行一次" << endl;
}
// 🔴 禁用拷贝 + 禁用赋值
Singleton(const Singleton&) = delete;
Singleton& operator=(const Singleton&) = delete;
};
// 测试
int main()
{
// 获取唯一实例
Singleton* p1 = Singleton::getInstance();
Singleton* p2 = Singleton::getInstance();
// p1 和 p2 指向同一个对象!
p1->show();
p2->show();
return 0;
}
2.死锁
2.1死锁的概念
死锁就是在线程中的以下情况:线程1和线程2都需要两把琐,分别是A琐和B琐,但是线程1在竞争琐的时候拿到了A琐,线程2拿到了B琐,现在他们都差一个琐,可是谁也不放手,这时候这两个琐都抱着自己的琐,在对方的琐哪里被挂起,这样的情况就成为"死锁";


2.2死锁的四个必要条件
①互斥条件:一个资源每次只能被一个执行流使用;也就是说因为需要互斥,所以才要有琐,有琐了才会有死锁,这不就成了死锁产生的条件了?
②请求与保持条件:一个执行流因请求资源而被阻塞时,对以获取的资源保持不放;

③不剥夺条件:一个执行流已获取的资源,在未使用完之前,不能强行剥夺

④循环等待条件:若在执行流之间新城一种头尾相连的循环等待资源的关系;也就是没办法向别的线程要锁,只能彼此要;但是也可能A向B要,B向C要,C又向A要,这样就形成了环路;也可能是个八个;

2.3 如何避免死锁
如何破坏?就是破坏掉死锁的四个必要条件的任何一个;
①破坏互斥条件:就是不要用琐了;
②破坏请求与保持条件:就是我想要你的琐,但是你不给我,呢我就释放掉我有的琐,也就是破坏掉保持的条件;
③破坏循环等待条件:保持申请琐的顺序一致,也就是我的申请顺序是A,B,另一个线程的申请顺序也是A,B;这样就可以在一定程度上破坏环的问题;也就是资源一次性分配,使用超时机制,加锁顺序一致;其实也可以使用一把锁,将需要的两把锁保护起来
④破坏不剥夺条件:让线程带上优先级,优先级更高的可以拿到琐
2.4 STL、智能指针、线程安全问题
①STL容器是否线程安全
STL容器为了保证高效,都没有加锁,而且stl容器都有自己的空间配置器,底层都需要new ,malloc这样的操作,所以存在线程安全问题;所以stl默认不是线程安全的,如果需要在多线程环境下使用,需要调用者自己加锁,保证对应的安全;
②智能指针是否线程安全
unique_ptr:因为unique_ptr不能拷贝,所以它是跟一个对象是强关联的,所以不存在线程安全问题;
shared_ptr:对于 shared_ptr, 多个对象需要共用⼀个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这个问题, 基于原子操作(CAS)的⽅式保证 shared_ptr 能够⾼效, 原⼦的操作引⽤计数.
注意:智能指针是线程安全的,但是并不是说智能指针指向的对象是安全的;
