线程安全的单例模式 | 可重入 | 线程安全 |死锁(理论)

news2024/10/6 20:53:25

在这里插入图片描述

🌈个人主页: 南桥几晴秋
🌈C++专栏: 南桥谈C++
🌈C语言专栏: C语言学习系列
🌈Linux学习专栏: 南桥谈Linux
🌈数据结构学习专栏: 数据结构杂谈
🌈数据库学习专栏: 南桥谈MySQL
🌈Qt学习专栏: 南桥谈Qt
🌈菜鸡代码练习: 练习随想记录
🌈git学习: 南桥谈Git

🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈🌈
本科在读菜鸡一枚,指出问题及时改正

文章目录

  • 单例模式概述
  • 饿汉实现方式和懒汉实现方式
    • 懒汉方式实现
      • 在单线程场景中
      • 多线程场景中
  • 可重入vs线程安全
  • 常见锁概念
    • 死锁
    • 死锁四个必要条件
    • 避免死锁
    • 避免死锁算法
  • STL、智能指针与线程安全
    • STL中的容器是否是线程安全的
    • 智能指针是否是线程安全的
  • 其他常见的各种锁

单例模式概述

某些类, 只应该具有一个对象(实例), 就称之为单例。
例如一个男人只能有一个媳妇。

在很多服务器开发场景中, 经常需要让服务器加载很多的数据 (上百G) 到内存中。此时往往要用一个单例的类来管理这些数据。

饿汉实现方式和懒汉实现方式

如何理解饿汉方式和懒汉方式?
饿汉方式:吃完饭,直接洗完,下一次吃饭的时候就可以直接使用;
懒汉方式:吃完饭,先放着,等下一顿吃饭的时候再去洗碗。

懒汉方式最核心的思想是 “延时加载”,从而能够优化服务器的启动速度。

懒汉方式实现

在单线程场景中

//ThreadPool.hpp
#pragma once

#include<iostream>
#include<unistd.h>
#include<string>
#include<vector>
#include<queue>
#include<functional>
#include"Thread.hpp"
#include"Log.hpp"

using namespace threadModel;
using namespace log_ns;

static const int gdefaultnum=5;

void test()
{
    while(true)
    {
        std::cout<<"hello world"<<std::endl;
        sleep(1);
    }
}

template<typename T>
class ThreadPool
{
private:
    void LockQueue()
    {
        pthread_mutex_lock(&_mutex);
    }

    void UnlockQueue()
    {
        pthread_mutex_unlock(&_mutex);
    }

    void Wakeup()
    {
        pthread_cond_signal(&_cond);
    }

    void WakeupAll()
    {
        pthread_cond_broadcast(&_cond);
    }

    void Sleep()
    {
        pthread_cond_wait(&_cond,&_mutex);
    }

    bool IsEmpty()
    {
        return _task_queue.empty();
    }

    void HandlerTask(const std::string& name)  // this
    {
        while (true)
        {
            LockQueue();
            //如果队列为空(有任务)
            while(IsEmpty()&&_isrunning) //线程没有任务,但是在工作,继续休眠
            {
                _sleep_thread_num++;
                LOG(INFO,"%s thread sleep begin!\n",name.c_str());

                Sleep();

                LOG(INFO,"%s thread wakeup!\n",name.c_str());

                _sleep_thread_num--;

            }

            if(IsEmpty()&&!_isrunning) // 任务是空的,并且线程退出工作
            {
                UnlockQueue();
                LOG(INFO,"%s quit\n",name.c_str());
                break;
            }

            // 队列不为空,有任务 或者 队列被唤醒
            // 取任务
            T t=_task_queue.front();
            _task_queue.pop();
            UnlockQueue();

            // 此处任务已经不在任务队列中,任务已经被拿走,处理任务和临界资源是两码事

            t(); // 处理任务,不能不用也不能在临界区中处理
            LOG(DEBUG,"hander task done, task is: \n%s",t.result().c_str());
        }
    }

    void Init()
    {
        func_t func = std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1);
        for (int i = 0; i < _thread_num; i++)
        {
            std::string threadname = "thread-" + std::to_string(i + 1);
            _threads.emplace_back(threadname, func);
            LOG(DEBUG, "construct thread %s done, init success.\n", threadname.c_str());
        }
    }

    void Start()
    {
        _isrunning = true;
        for (auto &thread : _threads)
        {
            LOG(DEBUG, "Start thread %s done.\n", thread.Name().c_str());
            thread.Start();
        }
    }

    ThreadPool(int thread_num = gdefaultnum)
        : _thread_num(thread_num)
        , _isrunning(false) // 刚开始线程没有使用
        ,_sleep_thread_num(0)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }

    ThreadPool(const ThreadPool<T> &)=delete;
    void operator=(const ThreadPool<T> &)=delete;

public:
    void Stop()
    {
        LockQueue();
        _isrunning=false;
        WakeupAll();
        UnlockQueue();
        LOG(INFO,"Thread Pool Stop Success!\n");
    }

    static ThreadPool<T> *GetInstance()
    {
        if(_tp==nullptr)
        {
            LOG(INFO,"create threadpool\n");
            _tp=new ThreadPool();
            _tp->Init();
            _tp->Start();
        }
        else
        {
            LOG(INFO,"get threadpool\n");
            
        }   

        return _tp;
    }

    void Equeue(const T &in)
    {
        LockQueue();
        if(_isrunning)
        {
            _task_queue.push(in);
            // 如果当前有线程在等待,需要唤醒
            if(_sleep_thread_num>0)
            {
                Wakeup();
            }
        }

        UnlockQueue();
    }

    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }
private:
    int _thread_num;
    std::vector<Thread> _threads;  // 管理多个线程
    std::queue<T> _task_queue; // 任务队列
    bool _isrunning; //当前线程是否在工作
    int _sleep_thread_num;   //计数器:休眠的线程个数

    pthread_mutex_t _mutex;
    pthread_cond_t _cond;

    //单例程模式
    static ThreadPool<T>* _tp;

};


//静态指针初始化必须在类外初始化
template<typename T>
ThreadPool<T> *ThreadPool<T>:: _tp=nullptr;

定义了一个静态成员函数 GetInstance(),用于实现线程池的单例模式:

  • 单例模式:
    这个函数的目的是确保 ThreadPool 类只有一个实例存在。它利用静态指针 _tp 来检查是否已经创建了一个实例。
  • 实例化逻辑:
    空指针检查:
    if (_tp == nullptr):检查静态指针 _tp 是否为空。如果为空,表示尚未创建线程池实例。
    创建实例:
    在指针为空的情况下,会记录日志(LOG(INFO, "create threadpool\n");),然后使用new关键字创建一个新的 ThreadPool 实例。接着调用 Init() 方法进行初始化,可能用于设置线程池的初始状态。然后调用 Start() 方法启动线程池,以便开始处理任务。
    获取现有实例:
    如果 _tp 不为空,说明线程池实例已存在,则记录另一条日志(LOG(INFO, "get threadpool\n");)以指示已经获取到现有实例。

通过检查静态指针 _tp 的状态来实现线程池的单例模式。它在第一次调用时创建并初始化线程池实例,随后的调用将返回相同的实例,从而避免不必要的资源浪费和多重实例的问题。这就是按需加载。

在这里插入图片描述

多线程场景中

//ThreadPool.hpp
#pragma once

#include<iostream>
#include<unistd.h>
#include<string>
#include<vector>
#include<queue>
#include<functional>
#include"Thread.hpp"
#include"Log.hpp"
#include"LockGuard.hpp"

using namespace threadModel;
using namespace log_ns;

static const int gdefaultnum=5;

void test()
{
    while(true)
    {
        std::cout<<"hello world"<<std::endl;
        sleep(1);
    }
}

template<typename T>
class ThreadPool
{
private:
    void LockQueue()
    {
        pthread_mutex_lock(&_mutex);
    }

    void UnlockQueue()
    {
        pthread_mutex_unlock(&_mutex);
    }

    void Wakeup()
    {
        pthread_cond_signal(&_cond);
    }

    void WakeupAll()
    {
        pthread_cond_broadcast(&_cond);
    }

    void Sleep()
    {
        pthread_cond_wait(&_cond,&_mutex);
    }

    bool IsEmpty()
    {
        return _task_queue.empty();
    }

    void HandlerTask(const std::string& name)  // this
    {
        while (true)
        {
            LockQueue();
            //如果队列为空(有任务)
            while(IsEmpty()&&_isrunning) //线程没有任务,但是在工作,继续休眠
            {
                _sleep_thread_num++;
                LOG(INFO,"%s thread sleep begin!\n",name.c_str());

                Sleep();

                LOG(INFO,"%s thread wakeup!\n",name.c_str());

                _sleep_thread_num--;

            }

            if(IsEmpty()&&!_isrunning) // 任务是空的,并且线程退出工作
            {
                UnlockQueue();
                LOG(INFO,"%s quit\n",name.c_str());
                break;
            }

            // 队列不为空,有任务 或者 队列被唤醒
            // 取任务
            T t=_task_queue.front();
            _task_queue.pop();
            UnlockQueue();

            // 此处任务已经不在任务队列中,任务已经被拿走,处理任务和临界资源是两码事

            t(); // 处理任务,不能不用也不能在临界区中处理
            LOG(DEBUG,"hander task done, task is: \n%s",t.result().c_str());
        }
    }

    void Init()
    {
        func_t func = std::bind(&ThreadPool::HandlerTask, this, std::placeholders::_1);
        for (int i = 0; i < _thread_num; i++)
        {
            std::string threadname = "thread-" + std::to_string(i + 1);
            _threads.emplace_back(threadname, func);
            LOG(DEBUG, "construct thread %s done, init success.\n", threadname.c_str());
        }
    }

    void Start()
    {
        _isrunning = true;
        for (auto &thread : _threads)
        {
            LOG(DEBUG, "Start thread %s done.\n", thread.Name().c_str());
            thread.Start();
        }
    }

    ThreadPool(int thread_num = gdefaultnum)
        : _thread_num(thread_num)
        , _isrunning(false) // 刚开始线程没有使用
        ,_sleep_thread_num(0)
    {
        pthread_mutex_init(&_mutex, nullptr);
        pthread_cond_init(&_cond, nullptr);
    }

    ThreadPool(const ThreadPool<T> &)=delete;
    void operator=(const ThreadPool<T> &)=delete;

public:
    void Stop()
    {
        LockQueue();
        _isrunning=false;
        WakeupAll();
        UnlockQueue();
        LOG(INFO,"Thread Pool Stop Success!\n");
    }

    static ThreadPool<T> *GetInstance()
    {
        if(_tp==nullptr)
        {
            LockGuard lockguard(&_sig_mutex);  //解决多线程场景

            if(_tp==nullptr)
            {
                LOG(INFO,"create threadpool\n");
                _tp=new ThreadPool();
                _tp->Init();
                _tp->Start();
            }
            else
            {
                LOG(INFO,"get threadpool\n");

            }   
        }

        return _tp;
    }

    void Equeue(const T &in)
    {
        LockQueue();
        if(_isrunning)
        {
            _task_queue.push(in);
            // 如果当前有线程在等待,需要唤醒
            if(_sleep_thread_num>0)
            {
                Wakeup();
            }
        }

        UnlockQueue();
    }

    ~ThreadPool()
    {
        pthread_mutex_destroy(&_mutex);
        pthread_cond_destroy(&_cond);
    }
private:
    int _thread_num;
    std::vector<Thread> _threads;  // 管理多个线程
    std::queue<T> _task_queue; // 任务队列
    bool _isrunning; //当前线程是否在工作
    int _sleep_thread_num;   //计数器:休眠的线程个数

    pthread_mutex_t _mutex;
    pthread_cond_t _cond;

    //单例程模式
    static ThreadPool<T>* _tp;
    static pthread_mutex_t _sig_mutex;

};


//静态指针初始化必须在类外初始化
template<typename T>
ThreadPool<T> *ThreadPool<T>:: _tp=nullptr;

template<typename T>
pthread_mutex_t ThreadPool<T>::_sig_mutex=PTHREAD_MUTEX_INITIALIZER;

多线程场景中,在GetInstance()内部,需要创建一个 LockGuard 对象以自动加锁 _sig_mutex 互斥锁。这确保在进入临界区时,只有一个线程可以访问此代码块,以避免多个线程同时创建实例。

可重入vs线程安全

  • 线程安全:多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。

  • 重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。

如果一个函数可重入,那么在多线程调用时一定是安全的;如果一个函数不可重入,那么这个函数可能不是线程安全的。

常见锁概念

死锁

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

一个线程一把锁也可能出现死锁:当在给一个线程加锁的后,没有解锁而是继续加锁。

死锁四个必要条件

  • 互斥条件:一个资源每次只能被一个执行流使用
  • 请求与保持条件:一个执行流因请求资源而阻塞时,对已获得的资源保持不放
  • 不剥夺条件:一个执行流已获得的资源,在末使用完之前,不能强行剥夺
  • 循环等待条件:若干执行流之间形成一种头尾相接的循环等待资源的关系

避免死锁

  • 破坏死锁的四个必要条件
  • 加锁顺序一致
  • 避免锁未释放的场景
  • 资源一次性分配

避免死锁算法

  • 死锁检测算法(了解)
  • 银行家算法(了解)

STL、智能指针与线程安全

STL中的容器是否是线程安全的

不是.

原因是, STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响.
而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶).
因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全

智能指针是否是线程安全的

对于 unique_ptr, 由于只是在当前代码块范围内生效, 因此不涉及线程安全问题.
对于 shared_ptr, 多个对象需要共用一个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数.

其他常见的各种锁

  • 悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
  • 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。
  • CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2192887.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

业务封装与映射 -- OTUk/ODUk/OPUk比特速率和容量

介绍OTUk&#xff0c;ODUk&#xff0c;OPUk&#xff0c;OTUCn&#xff0c;ODUCn&#xff0c;OPUCn的比特速率和容量。 OTN支持超100 Gbit/s&#xff0c;100 Gbit/s&#xff0c;40 Gbit/s&#xff0c;10 Gbit/s&#xff0c;2.5 Gbit/s&#xff0c;1.25 Gbit/s等多种线路速率。 …

SAP MM学习笔记 - 豆知识10 - OMSY 初期化会计期间,ABAP调用MMPV/MMRV来批量更新会计期间(TODO)

之前用MMRV&#xff0c;MMPV来一次一个月来修改会计期间。 如果是老的测试机&#xff0c;可能是10几年前的&#xff0c;一次1个月&#xff0c;更新到当前期间&#xff0c;搞个100多次&#xff0c;手都抖。 SAP MM学习笔记 - 错误 M7053 - Posting only possible in periods 2…

Python水循环标准化对比算法实现

&#x1f3af;要点 算法区分不同水循环数据类型&#xff1a;地下水、河水、降水、气温和其他&#xff0c;并使用相应标准化降水指数、标准化地下水指数、标准化河流水位指数和标准化降水蒸散指数。绘制和计算特定的时间序列比较统计学相关性。使用相关矩阵可视化集水区和显示空…

每日OJ题_牛客_最长无重复子数组_滑动窗口_C++_Java

目录 牛客_最长无重复子数组_滑动窗口 题目解析 C代码1暴力 C代码2滑动窗口 Java代码滑动窗口 牛客_最长无重复子数组_滑动窗口 最长无重复子数组_牛客题霸_牛客网 (nowcoder.com) 描述&#xff1a; 给定一个长度为n的数组arr&#xff0c;返回arr的最长无重复元素子数组…

【Linux】Ubuntu20.04上使用RabbitVCS的图形化SVN

文章目录 1、RabbitVCS1.1、RabbitVCS 介绍1.2、RabbitVCS 主要功能1.3、Ubuntu下 TortoiseSVN 替代者 2、安装2.1、命令安装2.2、安装使用2.3、使用权限 3、解决SVN无法保存密码问题3.1、问题描述3.2、解决方法 1、RabbitVCS 1.1、RabbitVCS 介绍 它是一款Linux系统下的图形…

jsencrypt实现js加密的另外一种方式(使用node-jsencrypt库)

在上一篇文章中&#xff0c;实现了使用jsencrypt模块RSA加密实现。 参考链接&#xff1a;记录使用crypto-js、jsencrypt实现js加密的方法-CSDN博客 在实现的过程中&#xff0c;会提示出错&#xff1a;ReferenceError: window is not defined &#xff0c;而且需要修改jsencry…

网站建设开发方法

在这个充满激烈竞争的网络世界&#xff0c;如何通过网站建设开发&#xff0c;打造一个引人注目、功能强大的在线空间&#xff0c;成为了许多人关注的焦点。 1. 初衷与定位&#xff1a; 在进行网站建设开发之前&#xff0c;首先需要明确网站的初衷和定位。是作为企业的官方展示…

AcWing 662:点的坐标 ← 结构体 or 三目运算符

【题目来源】https://www.acwing.com/problem/content/664/【题目描述】 给定两个保留一位小数的浮点数 X,Y&#xff0c;用来表示一个点的横纵坐标。 请你判断该点在坐标系中的位置。 【输入格式】 共一行&#xff0c;包含两个浮点数 X,Y&#xff0c;表示点的横纵坐标。【输出格…

ElasticSearch备考 -- Async search

一、题目 通过异步方式查询earthquakes索引下Magnitude大于5的数据 二、思考 正常的查询大家可能会用的多一点&#xff0c;这种异步查询为数据量比较大的查询在后台执行&#xff0c;不用同步等待结果&#xff0c;待执行完成在获取结果。 三、解题 Step 1、准备基础数据 # D…

【CV】带你跑通过线检测项目unbox_yolov5_deepsort_counting

文章目录 &#x1f315;运行结果&#x1f315;我的配置&#x1f315;下载项目&#x1f315;安装依赖&#x1f319;创建激活虚拟环境&#x1f319;pip install -r requirements.txt &#x1f315;确保有参数ckpt.t7和测试数据集test.mp4&#x1f315;运行&#x1f319;出现报错T…

Leecode热题100-560.和为k的子数组

给你一个整数数组 nums 和一个整数 k &#xff0c;请你统计并返回 该数组中和为 k 的子数组的个数 。 子数组是数组中元素的连续非空序列。 示例 1&#xff1a; 输入&#xff1a;nums [1,1,1], k 2 输出&#xff1a;2示例 2&#xff1a; 输入&#xff1a;nums [1,2,3], k…

k8s实战-1

k8s实战-1 一、资源创建方式1.命令行2.yaml 二、命名空间三、Pod总结 一、资源创建方式 1.命令行 就是直接通过命令的方式创建&#xff0c;比如我要创建namespace&#xff0c; kubectl create namespace hello删除&#xff1a; kubectl delete -f hello2.yaml 简单来说&am…

PCL 1.8.1 + VTK 1.8.0 + QT5.14.2+ VS2017 环境搭建

先看看效果: PCL 1.8.1下载安装: Tags PointCloudLibrary/pcl GitHub 安装完成后: 如果VTK想重新编译的,可以看我的这篇博客:

AI 智能名片商城小程序源码:构建 F2B2b2C 用户触达新生态

一、引言 1.1 研究背景 在当今数字化时代&#xff0c;企业对于用户的触达变得愈发关键。F2B2b2C 模式作为一种新兴的商业模式&#xff0c;旨在通过整合厂商&#xff08;F&#xff09;、批发商&#xff08;B&#xff09;、零售商&#xff08;b&#xff09;和消费者&#xff08…

南昌网站建设让你的企业网站更具竞争力

南昌网站建设让你的企业网站更具竞争力 在当今竞争激烈的市场环境中&#xff0c;一个高质量的网站不仅是企业形象的展示平台&#xff0c;更是吸引客户、提升业绩的重要工具。南昌作为江西的省会城市&#xff0c;互联网产业的蓬勃发展为企业网站建设提供了良好的机遇。 首先&am…

车载电子电气架构--- 车载诊断DTC全覆盖分类

我是穿拖鞋的汉子,魔都中坚持长期主义的汽车电子工程师。 老规矩,分享一段喜欢的文字,避免自己成为高知识低文化的工程师: 屏蔽力是信息过载时代一个人的特殊竞争力,任何消耗你的人和事,多看一眼都是你的不对。非必要不费力证明自己,无利益不试图说服别人,是精神上的节…

如何使用ssm实现基于Java的校园二手物品交易平台的设计与实现+vue

TOC ssm789基于Java的校园二手物品交易平台的设计与实现vue 绪论 1.1 研究背景 在这个推荐个性化的时代&#xff0c;采用新技术开发一个校园二手物品交易平台来分享和展示内容是一个永恒不变的需求。本次设计的校园二手物品交易平台有管理员&#xff0c;商家&#xff0c;用…

Leetcode - 周赛417

目录 一&#xff0c;3304. 找出第 K 个字符 I 二&#xff0c;3305. 元音辅音字符串计数 I 三&#xff0c;3307. 找出第 K 个字符 II 一&#xff0c;3304. 找出第 K 个字符 I 本题数据范围小&#xff0c;可以直接模拟&#xff0c;代码如下&#xff1a; class Solution {publ…