死锁+线程安全

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 能够⾼效, 原⼦的操作引⽤计数.

注意:智能指针是线程安全的,但是并不是说智能指针指向的对象是安全的;

相关推荐
饺子大魔王的男人2 小时前
Linux 下 Apache RocketMQ 部署与公网访问实现指南
linux·apache·rocketmq
专注API从业者2 小时前
淘宝 API 调用链路追踪实战:基于 SkyWalking/Pinpoint 的全链路监控搭建
大数据·开发语言·数据库·skywalking
jinanwuhuaguo2 小时前
OpenClaw v2026.4.1 深度剖析报告:任务系统、协作生态与安全范式的全面跃迁
java·大数据·开发语言·人工智能·深度学习
A.A呐2 小时前
【Linux第二十五章】高级IO
linux·运维·服务器
zzzsde2 小时前
【Linux】库的制作与使用(2)ELF&&静态链接
linux·运维·服务器
小邓的技术笔记2 小时前
Python 入门:从“其他语言”到 Pythonic 思维的完整迁移手册
开发语言·python
艾莉丝努力练剑2 小时前
【Linux线程】Linux系统多线程(二):线程的优缺点
linux·运维·服务器·c++·学习
yunn_2 小时前
Qt 多线程
c++·qt
啥咕啦呛2 小时前
跟着AI学java第4天:面向对象编程巩固
java·开发语言·人工智能