当前位置: 首页 > news >正文

【Linux】系统部分——线程安全与线程的单例模式

30.2 线程安全与线程的单例模式

文章目录

  • 30.2 线程安全与线程的单例模式
      • 线程安全
        • 常见的线程不安全的情况
        • 常⻅的线程安全的情况
        • 常⻅不可重⼊的情况
        • 常⻅可重⼊的情况
        • 死锁
      • 线程安全的单例模式
        • 懒汉方式实现单例模式
        • 饿汉方式实现单例模式
        • 将之前写的线程池改成单例模式(懒汉方式)

在前面实现了线程池的基本结构之后,我们需要从实操再回到理论,谈一谈线程安全有关的话题

线程安全

  • 线程安全:

    线程安全是指在多个线程访问公共资源时,程序能正确执行且不会出现互相干扰或数据不一致的情况。当多个线程并发运行同一段代码时,只有局部变量会被访问,而不会出现访问全局资源或未保护的共享资源的情况。运行期间不会出现数据不一致、崩溃或其他异常结果。例如,抢票时票数变为负数的情况就是线程不安全的体现。此外,使用STL容器或多线程操作自定义内存空间时,若未正确处理共享资源,也会导致线程安全问题。线程安全是多线程编程中最常见的问题之一,类似于C语言中指针问题的普遍性。

  • 重入:

    • 同⼀个函数被不同的执⾏流调⽤,当前⼀个流程还没有执⾏完,就有其他的执⾏流再次进⼊,我们称之为重⼊

    • 可重入函数是指同一个函数被不同执行流(如多线程或信号处理)重复调用时,不会出现任何问题。若函数在重入过程中出现问题,则称为不可重入函数。重入场景主要分为两种:多线程重复调用函数和信号导致的重入。在多进程环境中,函数虽然可能被重复调用,但由于变量是独立拷贝的,通常不会出现线程安- 全问题。而在多线程或信号处理中,由于共享资源的存在,可能导致重入问题。

线程安全与可重入性是两个不同维度的概念。可重入性描述的是函数能否被多个执行流重复调用而不出问题,而线程安全描述的是线程在执行过程中是否会出现资源冲突或数据不一致。

  • 一般情况下:可重入函数是线程安全的,因为其定义允许被多个执行流安全调用。

  • 但线程安全的函数不一定是可重入的,例如,若函数通过加锁保护全局资源,虽然多线程调用安全,但单线程递归调用时可能因未释放锁而导致死锁此时函数是线程安全但不可重入的。大多数函数是不可重入的,只有不访问任何全局资源的函数才可能是可重入的。线程安全问题通常是由于访问了不可重入函数或未保护的全局资源导致的

    举例说明:

    pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
    int global_counter = 0;// 一个线程安全的函数
    void increment_counter() {pthread_mutex_lock(&lock);global_counter++;pthread_mutex_unlock(&lock);
    }
    
    • 线程安全吗? 是安全的。多个线程同时调用它,global_counter 也能被正确递增。
    • 可重入吗? 不可重入
      • 想象一下,在单线程中:increment_counter() 被调用,它获得了锁。
      • 在它持有锁的期间,程序被一个信号中断,而信号处理函数也调用了 increment_counter()
      • 信号处理函数试图去获取同一把锁 lock,但这把锁已经被同一个线程持有了。
      • 这会导致死锁!该线程在等待自己释放锁,永远等不到。
常见的线程不安全的情况
  • 不保护共享变量的函数

  • 函数状态随着被调⽤,状态发⽣变化的函数

  • 返回指向静态变量指针的函数

  • 调⽤线程不安全函数的函数

常⻅的线程安全的情况
  • 每个线程对全局变量或者静态变量只有读取的权限,⽽没有写⼊的权限,⼀般来说这些线程是安全的
  • 类或者接⼝对于线程来说都是原⼦操作
  • 多个线程之间的切换不会导致该接⼝的执⾏结果存在⼆义性
常⻅不可重⼊的情况
  • 调⽤了malloc/free函数,因为malloc函数是⽤全局链表来管理堆的
  • 调⽤了标准I/O库函数,标准I/O库的很多实现都以不可重⼊的⽅式使⽤全局数据结构
  • 可重⼊函数体内使⽤了静态的数据结构
常⻅可重⼊的情况
  • 不使⽤全局变量或静态变量
  • 不使⽤malloc或者new开辟出的空间
  • 不调⽤不可重⼊函数
  • 不返回静态或全局数据,所有数据都有函数的调⽤者提供
  • 使⽤本地数据,或者通过制作全局数据的本地拷⻉来保护全局数据
死锁

死锁是指在⼀组进程中的各个进程均占有不会释放的资源,但因互相申请被其他进程所站⽤不会释放的资源⽽处于的⼀种永久等待状态

线程安全的单例模式

​ 线程安全的单例模式是一种设计模式,确保一个类只有一个实例,并提供一个全局访问点。

​ 线程池在使用时需要控制线程数量,根据CPU核心数合理设置线程数,通常为CPU核心数的两倍。线程池设计为单例模式便于统一管理和使用。单例模式分为饿汉式和懒汉式两种实现方式。饿汉式在类加载时就创建实例,懒汉式在首次使用时才创建实例。懒汉式通过延迟加载提高资源利用率,适用于体积较大的对象,可以加速程序启动时间。延迟加载的思想在Linux系统中广泛应用,如写时拷贝、缺页中断和内存地址空间申请等机制。

​ 懒汉⽅式最核⼼的思想是 “延时加载”. 从⽽能够优化服务器的启动速度

懒汉方式实现单例模式
template <typename T>
class Singleton
{static T *inst;public:static T *GetInstance(){if (inst == NULL){inst = new T();}return inst;}
};
饿汉方式实现单例模式
template <typename T>
class Singleton {static T data;
public:static T* GetInstance() {return &data;}
};
将之前写的线程池改成单例模式(懒汉方式)

要点:

  • 在类中直接创建单例,我们要禁止使用拷贝构造,拷贝赋值,并把构造函数设置为私有,防止外部调用构造函数创建对象

    class ThreadPool
    {
    private://....ThreadPool(const ThreadPool &) = delete;ThreadPool<T> &operator=(const ThreadPool &) = delete;ThreadPool(int num = defaultnum) : _num(num), _isrunning(false){for (int i = 0; i < _num; i++){_threads.push_back(std::make_shared<Thread>(std::bind(&ThreadPool::HanderTask, this, std::placeholders::_1)));LOG(LogLevel::INFO) << "创建线程: " << _threads.back()->Name() << "...Success";}}//.... }
  • 在类中直接创建单例,这个单例要设置为静态成员变量,并在类外定义

    namespace My_ThreadPool
    {class ThreadPool{private://....static ThreadPool<T> *instence;//....}template <class T>ThreadPool<T> *ThreadPool<T>::instence = NULL;
    }
    
  • 提供单例的静态成员函数

    public:
    //......
    static ThreadPool<T> *getInstence()
    {if (instence == NULL){LOG(LogLevel::INFO) << "单例首次被执行,需要加载对象...";instence = new ThreadPool<T>();}return instence;
    }
    //.......
    
  • 单例的使用方法

    ThreadPool<task_t>::getInstence()->Start();
    for(int i = 0; i < 10; i++)
    {ThreadPool<task_t>::getInstence()->Equeue(Push);sleep(1);
    }ThreadPool<task_t>::getInstence()->Stop();ThreadPool<task_t>::getInstence()->Wait();
    
  • 如果有多个线程使用这个单例——调用getInstence函数,由于单例是全局的对象,所以需要加锁

    namespace My_ThreadPool
    {class ThreadPool{public:static ThreadPool<T> *getInstence(){if (instance == NULL)//防止重复加锁{LockGuard lockguard(mutex);if (instence == NULL){LOG(LogLevel::INFO) << "单例首次被执行,需要加载对象...";instence = new ThreadPool<T>();}}return instence;}private://....static Mutex mutex;//....}template <typename T>Mutex ThreadPool<T>::mutex; // 只用来保护单例
    }
    

整个代码:

#pragma once#include <iostream>
#include <unistd.h>
#include <string>
#include <vector>
#include <queue>
#include <memory>
#include "Log.hpp"
#include "Mutex.hpp"
#include "Cond.hpp"
#include "Thread.hpp"namespace My_ThreadPool
{using namespace My_Mutex;using namespace My_Log;using namespace My_Cond;using namespace My_Thread;using thread_t = std::shared_ptr<Thread>;const static int defaultnum = 5;void DefaultTest(){while (true){LOG(LogLevel::DEBUG) << "Test......";sleep(1);}}template <class T>class ThreadPool{private:bool IsEmpty() { return _ptasks.empty(); }void HanderTask(std::string name){T t;LOG(LogLevel::INFO) << "线程: " << name << "执行HanderTask方法";while (true){{LockGuard lockguard(_lock);while (IsEmpty() && _isrunning){_wait_num++;_cond.Wait(_lock);_wait_num--;}if (IsEmpty() && !_isrunning)break;t = _ptasks.front();_ptasks.pop();}t(name);}LOG(LogLevel::INFO) << "线程: " << name << "退出HanderTask方法";}ThreadPool(const ThreadPool &) = delete;ThreadPool<T> &operator=(const ThreadPool &) = delete;ThreadPool(int num = defaultnum) : _num(num), _isrunning(false){for (int i = 0; i < _num; i++){_threads.push_back(std::make_shared<Thread>(std::bind(&ThreadPool::HanderTask, this, std::placeholders::_1)));LOG(LogLevel::INFO) << "创建线程: " << _threads.back()->Name() << "...Success";}}public:static ThreadPool<T> *getinstance(){if (instance == NULL){LockGuard lockguard(mutex);if (instance == NULL){LOG(LogLevel::INFO) << "单例首次被执行,需要加载对象...";instance = new ThreadPool<T>();}}return instance;}void Equeue(T t){LockGuard lockguard(_lock); // 加锁if (!_isrunning)return;_ptasks.push(t);if (_wait_num)_cond.Weak();}void Start(){if (_isrunning)return;_isrunning = true;for (auto &t : _threads){t->Start();LOG(LogLevel::INFO) << "启动线程: " << t->Name() << "...Success";}}void Wait(){for (auto &t : _threads){t->Jion();LOG(LogLevel::INFO) << "线程: " << t->Name() << "回收";}}void Stop(){if (_isrunning){_isrunning = false;if (_wait_num)_cond.WeakAll(); // 如果有线程在可变参数下等待,唤醒所有线程,把没有执行完成的任务(如果有)去执行}}~ThreadPool() {}private:std::vector<thread_t> _threads; // 这里储存的是一个一个的指向Thread的智能指针int _num;                       // 线程个数std::queue<T> _ptasks;          // 任务的指针队列Mutex _lock;Cond _cond;int _wait_num;bool _isrunning; // 线程池目前的工作状态static ThreadPool<T> *instance;static Mutex mutex;};template <class T>ThreadPool<T> *ThreadPool<T>::instance = NULL;template <typename T>Mutex ThreadPool<T>::mutex; // 只用来保护单例
}
#include "ThreadPool.hpp"
#include "Task.hpp"using namespace My_ThreadPool;int main()
{ENABLE_CONSOLE_LOG();ThreadPool<task_t>::getinstance()->Start();for(int i = 0; i < 10; i++){ThreadPool<task_t>::getinstance()->Equeue(Push);sleep(1);}ThreadPool<task_t>::getinstance()->Stop();ThreadPool<task_t>::getinstance()->Wait();return 0;
}

执行结果

user@iZ7xvdsb1wn2io90klvtwlZ:~/lesson34_3/SigThreadPool$ ./threadpool 
[2025-10-06 19:26:43] [INFO] [177014] [ThreadPool.hpp] [88] - 单例首次被执行,需要加载对象...
[2025-10-06 19:26:43] [INFO] [177014] [ThreadPool.hpp] [76] - 创建线程: thread-1...Success
[2025-10-06 19:26:43] [INFO] [177014] [ThreadPool.hpp] [76] - 创建线程: thread-2...Success
[2025-10-06 19:26:43] [INFO] [177014] [ThreadPool.hpp] [76] - 创建线程: thread-3...Success
[2025-10-06 19:26:43] [INFO] [177014] [ThreadPool.hpp] [76] - 创建线程: thread-4...Success
[2025-10-06 19:26:43] [INFO] [177014] [ThreadPool.hpp] [76] - 创建线程: thread-5...Success
[2025-10-06 19:26:43] [INFO] [177014] [ThreadPool.hpp] [112] - 启动线程: thread-1...Success
[2025-10-06 19:26:43] [INFO] [177014] [ThreadPool.hpp] [112] - 启动线程: thread-2...Success
[2025-10-06 19:26:43] [INFO] [177014] [ThreadPool.hpp] [112] - 启动线程: thread-3...Success
[2025-10-06 19:26:43] [INFO] [177014] [ThreadPool.hpp] [112] - 启动线程: thread-4...Success
[2025-10-06 19:26:43] [INFO] [177014] [ThreadPool.hpp] [112] - 启动线程: thread-5...Success
[2025-10-06 19:26:43] [INFO] [177014] [ThreadPool.hpp] [44] - 线程: thread-4执行HanderTask方法
[2025-10-06 19:26:43] [INFO] [177014] [ThreadPool.hpp] [44] - 线程: thread-1执行HanderTask方法
[2025-10-06 19:26:43] [DEBUG] [177014] [Task.hpp] [19] - 我是一个推送数据到服务器的一个任务, 我正在被执行[thread-4]
[2025-10-06 19:26:43] [INFO] [177014] [ThreadPool.hpp] [44] - 线程: thread-2执行HanderTask方法
[2025-10-06 19:26:43] [INFO] [177014] [ThreadPool.hpp] [44] - 线程: thread-5执行HanderTask方法
[2025-10-06 19:26:43] [INFO] [177014] [ThreadPool.hpp] [44] - 线程: thread-3执行HanderTask方法
[2025-10-06 19:26:44] [DEBUG] [177014] [Task.hpp] [19] - 我是一个推送数据到服务器的一个任务, 我正在被执行[thread-1]
[2025-10-06 19:26:45] [DEBUG] [177014] [Task.hpp] [19] - 我是一个推送数据到服务器的一个任务, 我正在被执行[thread-4]
[2025-10-06 19:26:46] [DEBUG] [177014] [Task.hpp] [19] - 我是一个推送数据到服务器的一个任务, 我正在被执行[thread-2]
[2025-10-06 19:26:47] [DEBUG] [177014] [Task.hpp] [19] - 我是一个推送数据到服务器的一个任务, 我正在被执行[thread-5]
[2025-10-06 19:26:48] [DEBUG] [177014] [Task.hpp] [19] - 我是一个推送数据到服务器的一个任务, 我正在被执行[thread-3]
[2025-10-06 19:26:49] [DEBUG] [177014] [Task.hpp] [19] - 我是一个推送数据到服务器的一个任务, 我正在被执行[thread-1]
[2025-10-06 19:26:50] [DEBUG] [177014] [Task.hpp] [19] - 我是一个推送数据到服务器的一个任务, 我正在被执行[thread-4]
[2025-10-06 19:26:51] [DEBUG] [177014] [Task.hpp] [19] - 我是一个推送数据到服务器的一个任务, 我正在被执行[thread-2]
[2025-10-06 19:26:52] [DEBUG] [177014] [Task.hpp] [19] - 我是一个推送数据到服务器的一个任务, 我正在被执行[thread-5]
[2025-10-06 19:26:53] [INFO] [177014] [ThreadPool.hpp] [65] - 线程: thread-3退出HanderTask方法
[2025-10-06 19:26:53] [INFO] [177014] [ThreadPool.hpp] [65] - 线程: thread-2退出HanderTask方法
[2025-10-06 19:26:53] [INFO] [177014] [ThreadPool.hpp] [65] - 线程: thread-5退出HanderTask方法
[2025-10-06 19:26:53] [INFO] [177014] [ThreadPool.hpp] [65] - 线程: thread-1退出HanderTask方法
[2025-10-06 19:26:53] [INFO] [177014] [ThreadPool.hpp] [65] - 线程: thread-4退出HanderTask方法
[2025-10-06 19:26:53] [INFO] [177014] [ThreadPool.hpp] [120] - 线程: thread-1回收
[2025-10-06 19:26:53] [INFO] [177014] [ThreadPool.hpp] [120] - 线程: thread-2回收
[2025-10-06 19:26:53] [INFO] [177014] [ThreadPool.hpp] [120] - 线程: thread-3回收
[2025-10-06 19:26:53] [INFO] [177014] [ThreadPool.hpp] [120] - 线程: thread-4回收
[2025-10-06 19:26:53] [INFO] [177014] [ThreadPool.hpp] [120] - 线程: thread-5回收
http://www.dtcms.com/a/449458.html

相关文章:

  • 关键词查询爱站网石家庄新闻头条新闻最新今天
  • HarmonyOS 应用开发深度解析:ArkTS 语法精要与现代化状态管理实践
  • wordpress站点图片多大合适做房产网站不备案可以吗
  • 【深入理解计算机网络04】通信基础核心知识全解析:从信号原理到物理层设备
  • 探索大语言模型(LLM):一文读懂通用大模型的定义、特点与分类
  • 江西网站建设价格东莞做网站 动点官网
  • 金融大数据处理与分析
  • 【Linux】基础开发工具---yum / apt
  • 做的好看的网站中国免费最好用建站cms
  • 项目---网络通信组件JsonRpc
  • 【数字孪生】03-交通数字孪生的架构和关键技术(1)
  • 构建AI智能体:五十五、混合式智能投资顾问:融合快速响应与深度推理的自适应架构
  • 网站建设需求填表你认为优酷该网站哪些地方可以做的更好_为什么?
  • 好用的软件下载网站网站恶意点击软件
  • 软考 系统架构设计师系列知识点之杂项集萃(170)
  • 排序算法比较
  • Learning To Rank
  • 建模布线8
  • 第十六章:固本培元,守正出奇——Template Method的模板艺术
  • 网站开发管理学什么3建设营销型网站流程图
  • 【论文阅读】-《SparseFool: a few pixels make a big difference》
  • SLAM中的非线性优化-2D图优化之视觉惯性VIO(二十-终结篇)
  • 如何做自己的游戏网站介绍自己做的电影网站
  • Qt Creator配置git插件功能
  • 【大前端】Vue 和 React 的区别详解 —— 两大前端框架深度对比
  • 衡阳网站搜索引擎优化服务器维护要多久
  • 网站改版需要注意什么网站开发注册流程以及收费
  • 大模型开发 - 02 Spring AI Concepts
  • SpringAI指标监控
  • 在国内网络环境下高效配置与使用 Flutter