辽宁省精神文明建设工作三大创建活动网站长沙公司建设网站
2026/3/28 8:48:34 网站建设 项目流程
辽宁省精神文明建设工作三大创建活动网站,长沙公司建设网站,一般通过血液传染的病有哪些,湖南环保设备公司中企动力网站建设技术支持单例模式什么是单例模式#xff1f;单例模式是一种创建型设计模式#xff0c;它保证 一个类只有一个实例#xff0c;并提供一个全局访问点。就像 一个国家只有一个总统 。核心特点唯一性 #xff1a;内存中只能有一个对象。全局访问 #xff1a;任何地方都可以通过 GetIns…单例模式什么是单例模式单例模式是一种创建型设计模式它保证一个类只有一个实例并提供一个全局访问点。就像 一个国家只有一个总统 。核心特点唯一性 内存中只能有一个对象。全局访问 任何地方都可以通过 GetInstance () 访问它。私有构造 构造函数必须私有 (private)防止外部 new 。禁拷贝 拷贝构造和赋值运算符必须禁用 (delete)。现在我们要对下面这个很大的类做一个单例模式class BigData { public: BigData() { std::cout [BigData] 构造函数被调用 (加载100GB数据...) std::endl; } void process() { std::cout [BigData] 正在处理数据... std::endl; } };饿汉模式原理 “饿了就要吃” 。程序一启动 main 函数执行前就立马把对象创建好。class SingletonEager { private: static BigData data; // 静态成员对象 SingletonEager() {} // 私有构造函数禁止外部创建 SingletonEager(const SingletonEager) delete; SingletonEager operator(const SingletonEager) delete; public: static BigData* GetInstance() { return data; } }; // 在类外初始化静态成员 BigData SingletonEager::data;优点 天然线程安全 C 保证静态变量在 main 之前初始化此时还没多线程。执行效率高 获取实例时不需要加锁判断。缺点 启动慢 如果对象很大加载 100G 数据程序启动会卡很久。浪费内存 如果程序运行了一整天都没用到它这 100G 内存就白占了。懒汉模式 - 线程不安全原理 “懒得动要用才去洗碗” 。第一次调用 GetInstance 时才创建。问题 多线程环境下A 线程判断 inst NULL 准备创建还没创建完B 线程也判断 inst NULL 于是两人都创建了一份。class SingletonLazyUnsafe { private: static BigData* inst; SingletonLazyUnsafe() {} SingletonLazyUnsafe(const SingletonLazyUnsafe) delete; SingletonLazyUnsafe operator(const SingletonLazyUnsafe) delete; public: static BigData* GetInstance() { if (inst NULL) { inst new BigData(); } return inst; } }; BigData* SingletonLazyUnsafe::inst NULL;懒汉模式 - 线程安全版这是经典的生产级实现。class SingletonLazySafe { private: // volatile: 防止编译器对代码进行过度优化例如指令重排 // 确保多线程下 inst 的可见性和有序性。 volatile static BigData* inst; static std::mutex _mtx; SingletonLazySafe() {} SingletonLazySafe(const SingletonLazySafe) delete; SingletonLazySafe operator(const SingletonLazySafe) delete; public: static BigData* GetInstance() { // 第一重检查如果已经创建了直接返回避免每次都加锁性能关键 if (inst NULL) { _mtx.lock(); // 加锁 // 第二重检查防止在加锁等待期间别人已经创建了 if (inst NULL) { inst new BigData(); } _mtx.unlock(); // 解锁 } return (BigData*)inst; } }; volatile BigData* SingletonLazySafe::inst NULL; std::mutex SingletonLazySafe::_mtx;注意点 双重 if 外层 if 挡住 99% 的请求避免锁竞争内层 if 保证安全性。volatile volatile static T* inst;防止编译器优化指令重排在某些老旧编译器或特定硬件上 new 操作可能被乱序导致返回未完全构造的对象。Meyers Singleton如果你用的是 C11 及以上这是最推荐的写法。class SingletonMeyers { private: SingletonMeyers() {} public: static BigData GetInstance() { // C11 规定局部静态变量的初始化是线程安全的 static BigData instance; return instance; } };原理 C11 标准明确规定局部静态变量的初始化是线程安全的。编译器会自动加锁保护初始化过程。优点 代码极少既是懒汉第一次调用才初始化又是线程安全的还没指针管理的麻烦。总结建议模式启动速度运行时性能线程安全推荐指数饿汉慢快 (无锁)是⭐⭐ (仅限小对象)懒汉 (不安全)快快否❌ (禁止使用)懒汉 (DCL)快中 (首次加锁)是⭐⭐⭐ (旧标准 / 复杂控制)Meyers (局部静态)快快是⭐⭐⭐⭐⭐ (C11 首选)关于volatile你可能以为inst new T();是一个原子操作要么做完要么没做但实际上编译器会把它拆成三步独立的指令// 伪代码new T() 的实际执行步骤 1. 分配内存给 T 类型的对象申请一块内存空间比如 malloc 2. 初始化对象调用 T 的构造函数给这块内存赋值比如初始化成员变量 3. 指针赋值把 inst 指针指向刚分配的内存地址。正常情况下CPU 按「1→2→3」执行inst 只有在对象完全构造好后才会非 NULL这没问题。但是为了提升执行效率编译器或 CPU会对没有数据依赖的指令做「指令重排」这就是 “过度优化” 的核心。对于上面的三步编译器会认为“步骤 2初始化对象和步骤 3指针赋值没有直接依赖”于是可能把顺序改成1. 分配内存 → 3. 指针赋值 → 2. 初始化对象这个重排对单线程完全无害但对「多线程的 DCL 场景」是致命的假设现在有线程 A 和线程 B执行流程如下线程 A 执行inst new T()被重排为「1→3→2」步骤 1分配了内存步骤 3inst 指针已经指向这块内存此时inst ! NULL步骤 2还没执行对象还没初始化是 “半成品”。线程 B 此时走到 DCL 的「第一重检查」if (inst NULL)发现inst ! NULL直接返回这个指针线程 B 拿到inst后试图调用对象的方法 / 访问成员变量 —— 但对象还没完成构造结果就是程序崩溃、数据错乱、逻辑异常比如访问未初始化的成员变量。volatile关键字的核心作用就是给编译器 / CPU 下 “禁令”针对被修饰的变量比如volatile static T* inst禁止指令重排编译器 / CPU 不能对涉及volatile变量的指令做重排 —— 也就是说inst new T()的三步必须严格按「1→2→3」执行inst只有在对象完全构造后才会非 NULL禁止缓存优化保证每次读写inst都是直接操作内存而不是缓存到 CP死锁死锁是指多个执行流进程 / 线程在执行过程中因争夺资源而造成的一种互相等待的现象。 如果没有外力干预它们都将无法推进下去程序就像 “卡死” 了一样。形象比喻两个人过独木桥A 在桥头占着位置等 B 让路B 在对面占着位置等 A 让路结果谁也过不去。死锁发生的四个必要条件这四个条件缺一不可只要破坏其中任意一个死锁就不会发生。互斥条件资源是独占的同一时刻只能被一个线程使用如互斥锁。这是锁的特性通常无法破坏。请求与保持条件吃着碗里的看着锅里的。线程已经持有了锁 A在不释放 A 的情况下去申请锁 B。不剥夺条件线程持有的资源在未用完之前不能被其他线程强行抢走。只能由它自己主动释放。循环等待条件A 等 BB 等 C...Z 等 A。形成了一个闭环。场景一死锁现场void deadlock_routine_A() { std::lock_guardstd::mutex lock1(mtx1); std::cout [线程A] 获取了 mtx1正在处理... std::endl; // 模拟处理耗时确保线程B有机会获取 mtx2形成死锁条件 std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::cout [线程A] 尝试获取 mtx2... std::endl; std::lock_guardstd::mutex lock2(mtx2); // 在这里阻塞等待 mtx2 std::cout [线程A] 成功获取 mtx2执行完毕。 std::endl; } void deadlock_routine_B() { std::lock_guardstd::mutex lock2(mtx2); std::cout [线程B] 获取了 mtx2正在处理... std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(100)); std::cout [线程B] 尝试获取 mtx1... std::endl; std::lock_guardstd::mutex lock1(mtx1); // 在这里阻塞等待 mtx1 std::cout [线程B] 成功获取 mtx1执行完毕。 std::endl; }结果[线程A] 获取了 mtx1... [线程B] 获取了 mtx2... [线程B] 尝试获取 mtx1... (等待) [线程A] 尝试获取 mtx2... (等待) (程序永久卡死)这就构成了典型的环路等待A - mtx2 - B - mtx1 - A。破解法一统一加锁顺序void safe_routine_ordered1() { std::lock_guardstd::mutex lock1(mtx1); std::cout [SafeThread1] 获取了 mtx1 std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::lock_guardstd::mutex lock2(mtx2); std::cout [SafeThread1] 获取了 mtx2 std::endl; } void safe_routine_ordered2() { std::lock_guardstd::mutex lock1(mtx1); std::cout [SafeThread2] 获取了 mtx1 std::endl; std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::lock_guardstd::mutex lock2(mtx2); std::cout [SafeThread2] 获取了 mtx2 std::endl; }破解法二使用 std::lock (C标准库算法)void safe_routine_std_lock() { // defer_lock 表示初始化时不立即加锁 std::unique_lockstd::mutex lock1(mtx1, std::defer_lock); std::unique_lockstd::mutex lock2(mtx2, std::defer_lock); std::cout [StdLockThread] 尝试同时获取 mtx1 和 mtx2... std::endl; // 原子性地锁定两个锁避免死锁的核心 std::lock(lock1, lock2); std::cout [StdLockThread] 成功获取双锁 std::endl; // 退出作用域时自动解锁 }原理std::lock 内部使用了一种死锁避免算法通常是 Try-and-Backoff 机制尝试锁住 lock1。尝试锁住 lock2。如果锁住 lock2 失败被别人占了它会主动释放 lock1破坏请求与保持条件。等待一小会儿然后重试直到同时拿到两把锁。破解法三使用超时锁 (破坏不剥夺)使用 try_lock_for。我尝试等 1 秒如果拿不到锁 B我就把自己手里的锁 A 释放掉过会再来。在实际开发中策略 1 (固定顺序) 和 策略 2 (std::lock) 是最有效的手段。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询