Linux44+45:日志和线程池

线程池 引入

日志和线程池

预制菜

内存池-------->减少系统调用的次数(效率高)

malloc或者stl里面的扩容操作:本质都是在向OS要内存->系统调用->是由成本的(事件成本)

结论:线程池也是一种"池化"技术

日志概念:

printf cout:向显示器文件上打

⽇志认识

计算机中的⽇志是记录系统和软件运⾏中发⽣事件的⽂件,主要作⽤是监控运⾏状态、记录异常信 息,帮助快速定位问题并⽀持程序员进⾏问题修复。它是系统维护、故障排查和安全管理的重要⼯ 具。

⽇志格式以下⼏个指标是必须得有的

• 时间戳

• ⽇志等级

• ⽇志内容 以下⼏个指标是可选的

• ⽂件名⾏号

• 进程,线程相关id信息等

日志等级:

DEBUG:测试

INFO:常规

WARING:报警

ERROR:错误

FATAL:致命错误

日志是衡量软件健康状态的信息。

日志等级是衡量的指标。

日志功能:

1.形成完整的日志

2.刷新到目标文件(显示器、指定文件打印日志)

策略模式:确定是显示器还是文件

日志代码:

cpp 复制代码
#pragma once
#include <iostream>
#include <cstdio>
#include <string>
#include <filesystem> //C++17
#include <sstream>
#include <fstream>
#include <memory>
#include <ctime>
#include <unistd.h>
#include "Mutex.hpp"

namespace LogModule
{
    using namespace MutexModule;
    using namespace std;

    const string gesp = "\r\n"; //日志的经典后缀
    // 线程池的策略
    class LogStrategy
    {
    public:
        ~LogStrategy() = default;                        // 默认析构
        virtual void SyncLog(const string &message) = 0; // 纯虚函数,自身没有实现方法
    };
    // 显示器显示
    class ConsoleLogStrategy : public LogStrategy        //继承
    {
    public:
        ConsoleLogStrategy() {}
        void SyncLog(const string &message) override    //重写
        {
            LockGuard lockguard(_mutex);
            cout << message << gesp;
        }
        ~ConsoleLogStrategy() {};

    private:
        Mutex _mutex; // 显示器是资源应当上锁
    };

    // 文件指定写入
    const string depath = "./log";
    const string defilename = "my.log";

    class FileLogStrategy : public LogStrategy
    {
    public:
        FileLogStrategy(string path = depath, string filename = defilename)//设定默认的路径和名字
            : _path(path),
              _filename(filename)
        {
            if (filesystem::exists(_path))//检验是否存在该路径,存在继续,不存在创建
            {
                return;
            }
            try
            {
                filesystem::create_directories(_path);    //创建该路径
            }
            catch (const filesystem::filesystem_error &e)  //创建错误
            {
                cerr << e.what() << '\n';
            }
        }
        void SyncLog(const string &message)
        {
            LockGuard lockguard(_mutex);
            string filename = _path + (_path.back() == '/' ? "" : "/") + _filename;
            ofstream out(filename, ios::app); // 追加的方式打开写入
            out << message << gesp;            //向里面写入
            out.close();                        //关闭文件
        }

    private:
        string _path;     // 路径
        string _filename; // 文件名
        Mutex _mutex;
    };

    enum class LogLevel        //日志等级
    {
        DEBUG,
        INFO,
        WARRING,
        ERROR,
        FATAL,
    };
    
    string Getcurtime()        // 得到时间
    {
        time_t cur = time(nullptr);
        struct tm cur_tm;            //struct tm时间戳
        localtime_r(&cur, &cur_tm);    
        char nowtime[128];
        snprintf(nowtime, sizeof(nowtime), "%4d-%02d-%02d-%02d-%02d-%02d", cur_tm.tm_year+1900, cur_tm.tm_mon+1, cur_tm.tm_mday, cur_tm.tm_hour, cur_tm.tm_min, cur_tm.tm_sec);
        return nowtime;
    }
    // level to str
    string Leveltostr(LogLevel level) //自定义转字符串
    {
        switch (level)
        {
        case LogLevel::DEBUG:
            return "BUG";
        case LogLevel::INFO:
            return "INFO";
        case LogLevel:: WARRING:
            return "WARRING";
        case LogLevel::ERROR:
            return "ERROR";
        default : 
            return "UNKONW";
        }
    }
    class Logger
    {
    public:
        Logger() //默认是显示屏打印
        {
            _fllush_strategy = make_unique<ConsoleLogStrategy>();
        }
        void EnableConsoleStrategy()    //显示
        {
            _fllush_strategy = make_unique<ConsoleLogStrategy>();
        }
        void EnableFileStrategy()    //文件
        {
            _fllush_strategy = make_unique<FileLogStrategy>();
        }

        class LogMessage
        {
        public:
            LogMessage(LogLevel level, string filename, int line_num, Logger &logger)
                : _curtime(Getcurtime()),
                  _level(level),    
                  _pid(getpid()),
                  _filename(filename),
                  _line_num(line_num),
                  _logger(logger)
            {
                // 把左半部分拼接起来
                stringstream ss;
                ss << "[" << _curtime << "]"
                   << "[" <<Leveltostr(_level)<<"]" //自定义转字符串,否则无法执行
                   <<"["<<_pid<<"]"
                   <<"["<<_filename<<"]"
                   <<"["<<_line_num<<"]"
                   <<"-";
                _log_string_info+=ss.str();
            }
            //重载<<,当后面输入时,直接当里面的内容
            template<typename T>
            LogMessage& operator<<(const T&info)
            {
                stringstream ss;    //输入流充当字符串内容
                ss<<info;
                _log_string_info+=ss.str();    //拼接
                return *this;                   //返回
            }
            ~LogMessage()
            {
                if(_logger._fllush_strategy) //析构执行显示操作
                {
                    _logger._fllush_strategy->SyncLog(_log_string_info);
                }
            }
        private:
            string _curtime;         // 时间
            LogLevel _level;         // 等级
            pthread_t _pid;          // 线程pid
            string _filename;        // 当前文件名字
            int _line_num;           // 行号
            string _log_string_info; // 完整的内容
            Logger &_logger;
        };

        //重载()// 这里故意写成返回临时对象
        LogMessage operator()(LogLevel level,string filename,int line_num)//临时对象???
        {
            return LogMessage(level,filename,line_num,*this);
        }
        ~Logger(){}
    private:
        unique_ptr<LogStrategy> _fllush_strategy;
    };
    //全局
    Logger logger;
    #define LOG(level) logger(level, __FILE__, __LINE__)
    #define Enable_Console_Log_Strategy() logger.EnableConsoleStrategy()
    #define Enable_File_Log_Strategy() logger.EnableFileStrategy()
}

宏替换:

cpp 复制代码
 //全局
    Logger logger;
    #define LOG(level) logger(level, __FILE__, __LINE__)
    #define Enable_Console_Log_Strategy() logger.EnableConsoleStrategy()
    #define Enable_File_Log_Strategy() logger.EnableFileStrategy()

怎么使用 ?


线程池

线程池概念

⼀种线程使⽤模式。**线程过多会带来调度开销,**进⽽影响缓存局部性和整体性能。

⽽线程池维护着多 个线程,等待着监督管理者分配可并发执⾏的任务。这避免了在处理短时间任务时创建与销毁线程的 代价。

线程池不仅能够保证内核的充分利⽤,还能防⽌过分调度。可⽤线程数量应该取决于可⽤的并发处理器、处理器内核、内存、⽹络sockets等的数量。

线程池的应⽤场景:

需要⼤量的线程来完成任务,且完成任务的时间⽐较短。⽐如WEB服务器完成⽹⻚请求这样的任 务,使⽤线程池技术是⾮常合适的。因为单个任务⼩,⽽任务数量巨⼤,你可以想象⼀个热⻔⽹站 的点击次数。但对于⻓时间的任务,⽐如⼀个Telnet连接请求,线程池的优点就不明显了。因为 Telnet会话时间⽐线程的创建时间⼤多了

对性能要求苛刻的应⽤,⽐如要求服务器迅速响应客⼾请求。

• **接受突发性的⼤量请求,但不⾄于使服务器因此产⽣⼤量线程的应⽤。**突发性⼤量客⼾请求,在没 有线程池情况下,将产⽣⼤量线程,虽然理论上⼤部分操作系统线程数⽬最⼤值不是问题,短时间 内产⽣⼤量线程可能使内存到达极限,出现错误.

线程池的种类

a. 创建固定数量线程池,循环从任务队列中获取任务对象,获取到任务对象后,执⾏任务对象中 的任务接

b. 浮动线程池,其他同上 此处,我们选择固定线程个数的线程池。

这里我们实现第一种

线程池代码:

(1)构造函数

cpp 复制代码
ThreadPool(int tnum = N)
            : _tnum(tnum),
              _isrunning(false),
              _sleep_num(0)
        {
            //在线程池下创建线程
            for (int i = 0; i < _tnum; i++)
            {
                _threads.emplace_back([this]()
                                      { HandlerTask(); });
            }
        }

除了必要的初始化,在构造线程池的时候,我们还要创建对应数量的线程

【this】捕获当前对象,也才可以访问当前对象ThreadPool的成员变量和成员函数

(2)Start函数

cpp 复制代码
void Start() // 启动线程
        {
            if (_isrunning)
                return; // 已启动不可再启动
            _isrunning = true;
            for (auto &x : _threads)
            {
                x.Start();
                LOG(LogLevel::INFO) << "start new thread success: " << x.getName();
            }
        }

已经启动那就不可再次启动了。

调用线程启动函数

(3)Stop函数和HandlerTask函数

cpp 复制代码
 void Stop() // stop方法
        {
            if (!_isrunning)
                return;         // 如果已经停止,那么不可再次停止
            _isrunning = false; // 唤醒全部休眠线程
            WakeUpAll();        //
        }
        // HandlerTask方法
        void HandlerTask()
        {
            // 1.定义缓冲区,存储当前线程名字
            char name[128];
            pthread_getname_np(pthread_self(), name, sizeof(name));
            // 2.死循环,直到线程池结束
            while (true)
            {
                T t;
                {
                    // 3.自动上锁,保证线程安全
                    LockGuard lockguard(_mutex);
                    // 4.核心等待逻辑,没有任务,且线程池正在运行, 线程等待休眠
                    while (_task_q.empty()&& _isrunning)
                    {
                        _sleep_num++;
                        _cond.Wait(_mutex);
                        _sleep_num--;
                    }
                    // 5.任务池为空并且线程池结束,停止退出
                    if (!_isrunning && _task_q.empty()) 
                    {
                       LOG(LogLevel::INFO) << name << " 退出了, 线程池退出&&任务队列为空";
                        break;
                    }
                    // 6.线程休眠停止,一定有任务, 对任务进行处理
                    t = _task_q.front();
                    _task_q.pop();
                }
                t(); // 处理任务,需要在临界区内部处理吗?不需要,一旦把任务处理了,任务已经是线程私有的了!!
            }
        }
cpp 复制代码
void WakeUpAll() // 唤醒全部休眠线程
        {
            LockGuard lockguard(_mutex);
            // 休眠数量不为空,那就唤醒所有线程
            if (_sleep_num)
            {
                _cond.Broadcast(); // 唤醒全部前程
            }
            LOG(LogLevel::INFO) << "唤醒所有的休眠线程";
        }

当我们的线程池退出_isrunning == false

内部线程状态?

1.等待:等待状态

2.等待->唤醒:唤醒状态

3.处理任务:执行t()任务

线程退出条件:线程池退出的时候,内部的任务,应该被完全取完&&_isrunning(false)

"我们才可以让对应的线程退出"

否则,就不可以放线程池退出
解释:

(1)对于任务状态,随它执行即可

(2)对于等待和等待唤醒状态则:

首先,要把他们全部唤醒WakeUpAll()操作,使得其退出while()请求分配任务的状态,然后统一执行退出,线程终止

问题:为什么要把所有线程都唤醒?

处于休眠的线程无法被stop.无法被检测到标志位。

有任务处理完,没有的通过上述stop

但是在休眠的线程,不可以被直接stop,

所以,stop方法要唤醒所有线程。

唤醒后,检测到队列为空,又继续休眠,怎么办?

体哦阿健加上,队列为空,+_isrunning =true

(4)Join方法:

cpp 复制代码
  void Join()
        {    
            //if(!_isrunning)return;
            for(auto& x:_threads)
            {
                x.Join();
            }
        }

如果是先进行stop,把_isrunning设置为false,再uu里面对_isrunning进行判断,就会形成一个bug.

Join无法继续进行。

(5)Enqueue方法:

cpp 复制代码
 void WakeUpOne()
        {
            _cond.Signal();
            LOG(LogLevel::INFO) << "唤醒一个的休眠线程";
        }
cpp 复制代码
 bool Enqueue(const T &in) // 入队列
        {
            if (_isrunning)//线程stop了,那还想入队列,没门 
            {
                LockGuard lockguard(_mutex);
                _task_q.push(in);
                if (_threads.size() == _sleep_num)//全部休眠了就唤醒一个,不然的话就由醒着的线程来执行
                    WakeUpOne();
                return true;
            }
            return false;
        }

如果当前队列不在运行状态,那就不可以入队列
处于运行状态,上锁,推入任务队列,

如果全部都休眠了那就唤醒一个,否则就有已经醒着的线程执行

完整代码

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include "Log.hpp"
#include "Thread.hpp"
#include "Cond.hpp"
#include "Mutex.hpp"

namespace ThreadPoolModule
{
    using namespace std;
    using namespace MutexModule;
    using namespace CondModule;
    using namespace LogModule;
    using namespace ThreadModlue;

    static const int N = 5;
    template <typename T> // 设置模板,这玩意要紧挨着
    class ThreadPool
    {
    private:
        void WakeUpAll() // 唤醒全部休眠线程
        {
            LockGuard lockguard(_mutex);
            if (_sleep_num)
            {
                _cond.Broadcast(); // 唤醒全部前程
            }
            LOG(LogLevel::INFO) << "唤醒所有的休眠线程";
        }
        void WakeUpOne()
        {
            _cond.Signal();
            LOG(LogLevel::INFO) << "唤醒一个的休眠线程";
        }
        
    public:
       
        ThreadPool(int tnum = N)
            : _tnum(tnum),
              _isrunning(false),
              _sleep_num(0)
        {
            在线程池下创建线程
            for (int i = 0; i < _tnum; i++)
            {
                _threads.emplace_back([this]()
                                      { HandlerTask(); });
            }
        }
        void Start() // 启动线程
        {
            if (_isrunning)
                return; // 已启动不可再启动
            _isrunning = true;
            for (auto &x : _threads)
            {
                x.Start();
                LOG(LogLevel::INFO) << "start new thread success: " << x.getName();
            }
        }

        
        void Stop() // stop方法
        {
            if (!_isrunning)
                return;         // 如果已经停止,那么不可再次停止
            _isrunning = false; // 唤醒全部休眠线程
            WakeUpAll();        //
        }
        // HandlerTask方法
        void HandlerTask()
        {
            char name[128];
            pthread_getname_np(pthread_self(), name, sizeof(name));
            while (true)
            {
                T t;
                {
                    LockGuard lockguard(_mutex);
                    while (_task_q.empty()&& _isrunning)
                    {
                        _sleep_num++;
                        _cond.Wait(_mutex);
                        _sleep_num--;
                    }
                    if (!_isrunning && _task_q.empty()) // 线程退出的时候,内部任务被完全取完,并且_isrunning为false的时候,才可以完全停止
                    {
                       LOG(LogLevel::INFO) << name << " 退出了, 线程池退出&&任务队列为空";
                        break;
                    }
                    // 一定有任务
                    t = _task_q.front(); // 从q中获取任务,任务已经是线程私有的了!!!
                    _task_q.pop();
                }
                t(); // 处理任务,需要在临界区内部处理吗?1 0
            }
        }
        void Join()
        {
            for (auto &x : _threads)
            {
                x.Join();
            }
        }
        
        bool Enqueue(const T &in) // 入队列
        {
            if (_isrunning) // 线程stop了,那还想入队列,没门 
            {
                LockGuard lockguard(_mutex);
                _task_q.push(in); 
                if (_threads.size() == _sleep_num) // 全部休眠了就唤醒一个,不然的话就由醒着的线程来执行
                    WakeUpOne();
                return true;
            }
            return false;
        }
        ~ThreadPool()
        {
        }

    private:
        vector<Thread> _threads; // 线程池里面的线程
        int _tnum;               // 线程池里面的线程个数
        queue<T> _task_q;        // 任务列表
        bool _isrunning;         // 运行状态
        int _sleep_num;          // 休眠者个数

        Mutex _mutex; // 锁
        Cond _cond;   // 条件变量

      
    };
   
}

线程安全的单例模式

什么是单例模式

是一种常用的软件设计模式,**用于确保一个类只有一个实例,**并提供一个全局访问点来获取这个实例。这种模式在多线程环境中尤其有用,因为它可以确保资源的唯一性和线程安全

单例模式的特点

某些类,只应该具有⼀个对象(实例),就称之为单例. 例如⼀个男⼈只能有⼀个媳妇. 在很多服务器开发场景中,经常需要让服务器加载很多的数据(上百G)到内存中.此时往往要⽤⼀个单例 的类来管理这些数据.

两种方式实现:饿汉模式和懒汉模式

饿汉实现方式:

吃完饭,立即洗碗

懒汉实现模式:

吃完饭,等下一次吃饭再洗碗

饿汉⽅式实现单例模式:

cpp 复制代码
template <typename T>

class Singleton {
 static T data;
public:
 static T* GetInstance() {
 return &data;
 }
};

只要通过Singleton这个包装类来使⽤T对象,则⼀个进程中只有⼀个T对象的实例.

静态成员属于类,不属于对象。

static 加载到内存,成员讲究意境创建了,不需要我们再次创建。

利用static静态加载的原则。

懒汉⽅式实现单例模式:

cpp 复制代码
template <typename T>

class Singleton {
 static T* inst;

public:
 static T* GetInstance() {
 if (inst == NULL) {
 inst = new T();
 }
 return inst;
 }
};
存在⼀个严重的问题,线程不安全.
第⼀次调⽤GetInstance的时候,如果两个线程同时调⽤,可能会创建出两份T对象的实例.
但是后续再次调⽤,就没有问题了.

懒汉模式实现线程池保护代码:

cpp 复制代码
#pragma once
#include <iostream>
#include <string>
#include <vector>
#include <queue>
#include "Log.hpp"
#include "Thread.hpp"
#include "Cond.hpp"
#include "Mutex.hpp"

namespace ThreadPoolModule
{
    using namespace std;
    using namespace MutexModule;
    using namespace CondModule;
    using namespace LogModule;
    using namespace ThreadModlue;

    static const int N = 5;
    template <typename T> // 设置模板,这玩意要紧挨着
    class ThreadPool
    {
    private:
        void WakeUpAll() // 唤醒全部休眠线程
        {
            LockGuard lockguard(_mutex);
            if (_sleep_num)
            {
                _cond.Broadcast(); // 唤醒全部前程
            }
            LOG(LogLevel::INFO) << "唤醒所有的休眠线程";
        }
        void WakeUpOne()
        {
            _cond.Signal();
            LOG(LogLevel::INFO) << "唤醒一个的休眠线程";
        }
        //懒汉模式,(1)把构造函数和Start函数私有化
        ThreadPool(int tnum = N)
            : _tnum(tnum),
              _isrunning(false),
              _sleep_num(0)
        {
            //在线程池下创建线程
            for (int i = 0; i < _tnum; i++)
            {
                _threads.emplace_back([this]()
                                      { HandlerTask(); });
            }
        }
        void Start() // 启动线程
        {
            if (_isrunning)
                return; // 已启动不可再启动
            _isrunning = true;
            for (auto &x : _threads)
            {
                x.Start();
                LOG(LogLevel::INFO) << "start new thread success: " << x.getName();
            }
        }
    public:
        //1.第一种:常规(第二种:单例-》懒汉模式)
        // ThreadPool(int tnum = N)
        //     : _tnum(tnum),
        //       _isrunning(false),
        //       _sleep_num(0)
        // {
        //     在线程池下创建线程
        //     for (int i = 0; i < _tnum; i++)
        //     {
        //         _threads.emplace_back([this]()
        //                               { HandlerTask(); });
        //     }
        // }
        // void Start() // 启动线程
        // {
        //     if (_isrunning)
        //         return; // 已启动不可再启动
        //     _isrunning = true;
        //     for (auto &x : _threads)
        //     {
        //         x.Start();
        //         LOG(LogLevel::INFO) << "start new thread success: " << x.getName();
        //     }
        // }

        //懒汉模式-》设置Getinstance
        static ThreadPool<T>* GetInstance()
        {
            if(inc == nullptr)//第一面墙
            {
                LockGuard lockguard(_lock);
                if(inc == nullptr)
                {
                    inc = new ThreadPool<T>();
                    inc->Start();
                }
            }
            return inc;
        }
        void Stop() // stop方法
        {
            if (!_isrunning)
                return;         // 如果已经停止,那么不可再次停止
            _isrunning = false; // 唤醒全部休眠线程
            WakeUpAll();        //
        }
        // HandlerTask方法
        void HandlerTask()
        {
            char name[128];
            pthread_getname_np(pthread_self(), name, sizeof(name));
            while (true)
            {
                T t;
                {
                    LockGuard lockguard(_mutex);
                    while (_task_q.empty()&& _isrunning)
                    {
                        _sleep_num++;
                        _cond.Wait(_mutex);
                        _sleep_num--;
                    }
                    if (!_isrunning && _task_q.empty()) // 线程退出的时候,内部任务被完全取完,并且_isrunning为false的时候,才可以完全停止
                    {
                       LOG(LogLevel::INFO) << name << " 退出了, 线程池退出&&任务队列为空";
                        break;
                    }
                    // 一定有任务
                    t = _task_q.front(); // 从q中获取任务,任务已经是线程私有的了!!!
                    _task_q.pop();
                }
                t(); // 处理任务,需要在临界区内部处理吗?1 0
            }
        }
        void Join()
        {
            for (auto &x : _threads)
            {
                x.Join();
            }
        }
        
        bool Enqueue(const T &in) // 入队列
        {
            if (_isrunning) // 线程stop了,那还想入队列,没门 
            {
                LockGuard lockguard(_mutex);
                _task_q.push(in); 
                if (_threads.size() == _sleep_num) // 全部休眠了就唤醒一个,不然的话就由醒着的线程来执行
                    WakeUpOne();
                return true;
            }
            return false;
        }
        ~ThreadPool()
        {
        }

    private:
        vector<Thread> _threads; // 线程池里面的线程
        int _tnum;               // 线程池里面的线程个数
        queue<T> _task_q;        // 任务列表
        bool _isrunning;         // 运行状态
        int _sleep_num;          // 休眠者个数

        Mutex _mutex; // 锁
        Cond _cond;   // 条件变量

        //懒汉模式
        static ThreadPool<T>* inc;
        static Mutex _lock;
    };
    //懒汉模式初始化
    template<typename T> 
    ThreadPool<T>* ThreadPool<T>::inc = nullptr;
    
    template<typename T>
    Mutex ThreadPool<T>::_lock;
}

注意:

1.进程ui核心的思想就延迟加载。需要时才创建。

变量设置为指针的形式。

cpp 复制代码
 static ThreadPool<T>* inc;
 static Mutex _lock;

类内对象必须有对象,但懒汉模式把构造私有化,不允许创建对象,所以成员变量设置为static

2.不允许,单例进行构造和拷贝

cpp 复制代码
        ThreadPool(const ThreadPool<T>&) = delete;//不要拷贝
        ThreadPool &operator=(const ThreadPool<T>&) = delete;//不压迫赋值

3.初始化

cpp 复制代码
 template<typename T> 
    ThreadPool<T>* ThreadPool<T>::inc = nullptr;
    
    template<typename T>
    Mutex ThreadPool<T>::_lock;

4.私有化构造和Start

cpp 复制代码
private:
        
        //懒汉模式,(1)把构造函数和Start函数私有化
        ThreadPool(int tnum = N)
            : _tnum(tnum),
              _isrunning(false),
              _sleep_num(0)
        {
            //在线程池下创建线程
            for (int i = 0; i < _tnum; i++)
            {
                _threads.emplace_back([this]()
                                      { HandlerTask(); });
            }
        }
        void Start() // 启动线程
        {
            if (_isrunning)
                return; // 已启动不可再启动
            _isrunning = true;
            for (auto &x : _threads)
            {
                x.Start();
                LOG(LogLevel::INFO) << "start new thread success: " << x.getName();
            }
        }
        ThreadPool(const ThreadPool<T>&) = delete;//不要拷贝
        ThreadPool &operator=(const ThreadPool<T>&) = delete;//不压迫赋值

5.提供GetInstance接口

cpp 复制代码
 static ThreadPool<T>* GetInstance()
        {
            if(inc == nullptr)//第一面墙
            {
                LockGuard lockguard(_lock);//锁
                if(inc == nullptr)//第二面墙
                {
                    inc = new ThreadPool<T>();
                    inc->Start();
                }
            }
            return inc;
        }

(1)保护对象 定义一把锁static,静态成员无法直接访问类内属性

(2)类内的静态方法,无法访问类内属性,只能访问静态属性,所以成员变成inc设置为静态,就是为了可以调用类内的方法

(3)"三重保险":双端判断,一端加锁,

取消第一面墙:每一个进程进来获取锁,还要等,获取锁后判断,是否为空,效率低,很慢

进行双端加锁,双层判断,提高获取效率

线程安全和重⼊问题

概念

**线程安全:**就是多个线程在访问共享资源时,能够正确地执⾏,不会相互⼲扰或破坏彼此的执⾏结 果。⼀般⽽⾔,多个线程并发同⼀段只有局部变量的代码时,不会出现不同的结果。但是对全局变量 或者静态变量进⾏操作,并且没有锁保护的情况下,容易出现该问题。

重⼊:同⼀个函数被不同的执⾏流调⽤,当前⼀个流程还没有执⾏完,就有其他的执⾏流再次进⼊, 我们称之为重⼊ 。⼀个函数在重⼊的情况下,运⾏结果不会出现任何不同或者任何问题,则该函数被 称为可重⼊函数,否则,是不可重⼊函数。 学到现在,其实我们已经能理解重⼊其实可以分为两种情况

• 多线程重⼊函数

• 信号导致⼀个执⾏流重复进⼊函数

死锁

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

• 为了⽅便表述,假设现在线程A,线程B必须同时持有锁1和锁2,才能进⾏后续资源的访问

死锁四个必要条件

• 互斥条件:⼀个资源每次只能被⼀个执⾏流使⽤

• 请求与保持条件:⼀个执⾏流因请求资源⽽阻塞时,对已获得的资源保持不放

• 不剥夺条件:⼀个执⾏流已获得的资源,在末使⽤完之前,不能强⾏剥夺

• 循环等待条件:若⼲执⾏流之间形成⼀种头尾相接的循环等待资源的关系

STL,智能指针和线程安全

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

不是.

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

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

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

其他常⻅的各种锁

• 悲观锁:在每次取数据时,总是担⼼数据会被其他线程修改,所以会在取数据前先加锁(读锁, 写锁,⾏锁等),当其他线程想要访问数据时,被阻塞挂起。

• 乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新 数据前,会判断其他数据在更新前有没有对数据进⾏修改。主要采⽤两种⽅式:版本号机制和 CAS操作。

• CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则⽤新值更 新。若不等则失败,失败则重试,⼀般是⼀个⾃旋的过程,即不断重试。

• ⾃旋锁,读写锁,加餐课详细介绍

相关推荐
️是782 小时前
信息奥赛一本通—编程启蒙(3345:【例60.2】 约瑟夫问题)
开发语言·c++·算法
add45a2 小时前
C++中的智能指针详解
开发语言·c++·算法
做科研的周师兄2 小时前
巴音河中下游灌溉草地空间分布数据集(2020年)
大数据·人工智能·算法·机器学习·数据挖掘·聚类
闻缺陷则喜何志丹2 小时前
【分治法 前缀和】P8572 [JRKSJ R6] Eltaw|普及+
c++·算法·前缀和·洛谷·分治法
纤纡.2 小时前
矿物识别分类:8 种机器学习算法对比与实战(平均值填充数据集)
python·深度学习·算法·机器学习
ulias2122 小时前
函数栈帧的创建和销毁
开发语言·数据结构·c++·windows·算法
少许极端2 小时前
算法奇妙屋(三十五)-贪心算法学习之路 2
学习·算法·贪心算法
代码探秘者2 小时前
【算法篇】3.位运算
java·数据结构·后端·python·算法·spring
Aaswk2 小时前
回溯算法的本质理解
c语言·算法·leetcode·力扣·剪枝