【Linux系统编程】第五十弹---构建高效单例模式线程池、详解线程安全与可重入性、解析死锁与避免策略,以及STL与智能指针的线程安全性探究

个人主页: 熬夜学编程的小林

💗系列专栏: 【C语言详解】 【数据结构详解】【C++详解】【Linux系统编程】

目录

1、将日志加到线程池

1.1、Thread类

1.2、ThreadPool类

1.2.1、HandlerTask()

1.2.2、其他公有成员函数

1.3、主函数

2、单例版线程池

2.1、私有成员函数

2.2、获取对象函数

2.2.1、不加锁版本

2.2.2、加锁版本

3、可重入VS线程安全

3.1、概念

3.2、常见的线程不安全的情况

3.3、常见的线程安全的情况

3.4、常见不可重入的情况

3.5、常见可重入的情况

3.6、可重入与线程安全联系

3.7、可重入与线程安全区别

4、常见锁概念

4.1、死锁

4.2、死锁四个必要条件

4.3、避免死锁

4.4、避免死锁算法

5、STL,智能指针和线程安全


1、将日志加到线程池

1.1、Thread类

此处使用日志来打印消息,因此需要将Thread类的std::cout打印信息删除掉!!!

#pragma once
#include <iostream>
#include <string>
#include <functional>
#include <pthread.h>

namespace ThreadMoudle
{
    // typedef std::function<void()> func_t;
    using func_t = std::function<void(const std::string&)>;

    class Thread
    {
    public:
        void Excute()
        {
            _isrunning = true;
            _func(_name);
            _isrunning = false;
        }
    public:
        Thread(const std::string& name,func_t func):_name(name),_func(func)
        {}
        // 新线程执行该方法
        static void* ThreadRoutine(void* args)
        {
            Thread* self = static_cast<Thread*>(args);
            self->Excute();
            return nullptr;
        }
        std::string Status()
        {
            if(_isrunning)
                return "running";
            else 
                return "sleep";
        }
        bool Start()
        {
            // ::使用库函数接口,直接使用ThreadRoutine会报错,因为成员函数有隐含this指针 + static
            int n = ::pthread_create(&_tid,nullptr,ThreadRoutine,this);
            if(n != 0) return false;
            return true;
        }
        void Stop()
        {
            if(_isrunning)
            {
                ::pthread_cancel(_tid);
                _isrunning = false;
            }
        }
        std::string Name()
        {
            return _name;
        }
        void Join()
        {
            ::pthread_join(_tid,nullptr);
        }
        ~Thread()
        {}
    private:
        std::string _name;
        pthread_t _tid;
        bool _isrunning;
        func_t _func; // 线程要执行的回调函数
    };
}

1.2、ThreadPool类

1.2.1、HandlerTask()

将std::cout部分改为LOG打印!!!

// 处理任务,从任务队列取任务,加锁
void HandlerTask(const std::string &name)
{
    while (true)
    {
        // 1.取任务
        LockQueue();
        // 队列为空且运行则休眠
        while (IsEmpty() && _isrunning) // if?
        {
            _sleep_thread_num++; // 防止阻塞,因为一开始休眠线程数为0
            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)
        {
            // std::cout << name << " quit" << std::endl;
            LOG(INFO,"%s thread quit!\n",name.c_str());
            UnlockQueue();
            break;
        }
        // 有任务
        T t = _task_queue.front();
        _task_queue.pop();
        UnlockQueue();

        // 2.处理任务
        t(); // 处理任务(只属于自己线程),此处不用/不能在临界区处理
        // std::cout << name << ":" << t.result() << std::endl;
        LOG(DEBUG,"hander task done,task is : %s\n",t.result().c_str());
    }
}

1.2.2、其他公有成员函数

此处只展示需要修改的部分!!!

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,test/*TODO*/); // 按照构造函数尾插对象
        _threads.emplace_back(threadname, func);
        // _threads.emplace_back(threadname, HandlerTask);
        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();
    }
}
void Stop()
{
    LockQueue();
    _isrunning = false;
    WakeupAll(); // 防止有线程在等待
    UnlockQueue();
    LOG(INFO,"thread pool stop done!\n");
}

1.3、主函数

主函数执行5次之后,停止线程池!!!

// 测试日志版线程池
int main()
{
    ThreadPool<Task>* tp = new ThreadPool<Task>();
    tp->Init();
    tp->Start();
    int cnt = 5;
    // 执行5次循环结束
    while(cnt)
    {
        Task t(1,1);
        tp->Equeue(t);
        LOG(INFO,"equeue a task,%s\n",t.debug().c_str());
        cnt--;
        sleep(1);
    }
    // 新线程停止
    tp->Stop();
    LOG(INFO,"thread pool stop\n");
    // 5秒后主线程结束
    sleep(5);
    return 0;
}

运行结果 

2、单例版线程池

1、单例模式特点:某些类, 只应该具有一个对象(实例), 就称之为单例.

2、单例版(懒汉模式)线程池需要加两个成员变量静态线程池指针(获取类对象地址),静态互斥锁(解决多线程获取多个对象问题)!!!

注意:静态成员变量需要在类外初始化!!!

2.1、私有成员函数

为了实现单例模式需要将构造函数私有禁止拷贝构造和赋值操作符重载,并将初始化和启动线程池函数私有!!!

private:
    // 单例模式构造函数私有
    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);
    }
    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,test/*TODO*/); // 按照构造函数尾插对象
            _threads.emplace_back(threadname, func);
            // _threads.emplace_back(threadname, HandlerTask);
            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(const ThreadPool<T> &) = delete;     // 禁止拷贝构造
    void operator=(const ThreadPool<T> &) = delete; // 禁止赋值重载

2.2、获取对象函数

类外初始化静态成员

// 静态成员类外初始化
template <typename T>
ThreadPool<T> *ThreadPool<T>::_tp = nullptr;

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

2.2.1、不加锁版本

单进程单线程可以不用加锁!!!

// 单线程
static ThreadPool<T> *GetInstance()
{
    if (_tp == nullptr)
    {
        LOG(INFO, "create thread pool\n");
        _tp = new ThreadPool();
        _tp->Init();
        _tp->Start();
    }
    else
    {
        LOG(INFO, "get thread pool\n");
    }
    return _tp;
}

运行结果

测试是否为单例 

int main()
{
    // 测试是否为单例
    ThreadPool<Task>* tp1 = new ThreadPool<Task>();
    ThreadPool<Task> tp2 = *(ThreadPool<Task>::GetInstance());

    return 0;
}

运行结果 

2.2.2、加锁版本

多线程可能会有创建多个对象的情况,需加锁解决!!!

static ThreadPool<T> *GetInstance()
{
    // 只有第一次需要加锁,减少锁的竞争
    if (_tp == nullptr)
    {

        LockGuard lockguard(&_sig_mutex);
        if (_tp == nullptr)
        {
            LOG(INFO, "create thread pool\n");
            _tp = new ThreadPool();
            _tp->Init();
            _tp->Start();
        }
        else
        {
            LOG(INFO, "get thread pool\n");
        }
    }
    return _tp;
}

3、可重入VS线程安全

3.1、概念

  • 线程安全多个线程并发同一段代码时,不会出现不同的结果。常见对全局变量或者静态变量进行操作,并且没有锁保护的情况下,会出现该问题。
  • 重入:同一个函数被不同的执行流调用,当前一个流程还没有执行完,就有其他的执行流再次进入,我们称之为重入。一个函数在重入的情况下,运行结果不会出现任何不同或者任何问题,则该函数被称为可重入函数,否则,是不可重入函数。

3.2、常见的线程不安全的情况

  • 不保护共享变量的函数
  • 函数状态随着被调用,状态发生变化的函数
  • 返回指向静态变量指针的函数
  • 调用线程不安全函数的函数

3.3、常见的线程安全的情况

  • 每个线程对全局变量或者静态变量只有读取的权限,而没有写入的权限,一般来说这些线程是安全的
  • 类或者接口对于线程来说都是原子操作
  • 多个线程之间的切换不会导致该接口的执行结果存在二义性

3.4、常见不可重入的情况

  • 调用了malloc/free函数,因为malloc函数是用全局链表来管理堆的
  • 调用了标准I/O库函数,标准I/O库的很多实现都以不可重入的方式使用全局数据结构
  • 可重入函数体内使用了静态的数据结构

3.5、常见可重入的情况

  • 不使用全局变量或静态变量
  • 不使用用 malloc 或者 new 开辟出的空间
  • 不调用不可重入函数
  • 不返回静态或全局数据,所有数据都有函数的调用者提供
  • 使用本地数据,或者通过制作全局数据的本地拷贝来保护全局数据

3.6、可重入与线程安全联系

  • 函数是可重入的,那就是线程安全的
  • 函数是不可重入的,那就不能由多个线程使用,有可能引发线程安全问题
  • 如果一个函数中有全局变量,那么这个函数既不是线程安全也不是可重入的

3.7、可重入与线程安全区别

  • 可重入函数是线程安全函数的一种
  • 线程安全不一定是可重入的,而可重入函数则一定是线程安全的
  • 如果将对临界资源的访问加上锁,则这个函数是线程安全的,但如果这个重入函数若锁还未释放则会产生死锁,因此是不可重入的

4、常见锁概念

4.1、死锁

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

4.2、死锁四个必要条件

死锁就一定会出现下面的四个条件,但是出现下面四个条件不代表死锁!!!

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

4.3、避免死锁

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

4.4、避免死锁算法

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

5、STL,智能指针和线程安全

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

不是。原因是:

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于SSM的作业批改系统+LW示例参考

1.项目介绍 功能模块&#xff1a;管理员&#xff08;学生管理、教师管理、作业信息管理、作业提交管理、作业批改管理等&#xff09;、学生&#xff08;个人信息管理、作业提交、作业查看等&#xff09;、教师&#xff08;个人中心、作业创建、作业批改等&#xff09;技术选型…

RabbitMQ高可用延迟消息惰性队列

目录 生产者确认 消息持久化 消费者确认 TTL延迟队列 TTL延迟消息 惰性队列 生产者确认 生产者确认就是&#xff1a;发送消息的人&#xff0c;要确保消息发送给了消息队列&#xff0c;分别是确保到了交换机&#xff0c;确保到了消息队列这两步。 1、在发送消息服务的ap…

嵌入式面试八股文(十)·RS485特性分析、CAN硬件同步和再同步遵从规则、SPI四种工作模式、错误帧基本概念

目录 1. 相较于传统的RS232接口&#xff0c;RS485的接口特性有哪些&#xff1f; 2. 在CAN接口协议中硬件同步和再同步需要遵从哪些规则&#xff1f; 3. 为什么位错误不能用于帧间隔&#xff1f; 4. SPI四种工作模式&#xff1f; 5. 关于错误帧&#xff0c;基本概念&a…

librdns一个开源DNS解析库

原文地址&#xff1a;librdns一个开源DNS解析库 – 无敌牛 欢迎参观我的个人博客&#xff1a;无敌牛 – 技术/著作/典籍/分享等 介绍 librdns是一个开源的异步多功能插件式的解析器&#xff0c;用于DNS解析。 源代码地址&#xff1a;GitHub - vstakhov/librdns: Asynchrono…

cookie反爬----普通服务器,阿里系

目录 一.常见COOKIE反爬 普通&#xff1a; 1. 简介 2. 加密原理 二.实战案例 1. 服务器响应cookie信息 1. 逆向目标 2. 逆向分析 2. 阿里系cookie逆向 1. 逆向目标 2. 逆向分析 实战&#xff1a; 无限debugger原理 1. Function("debugger").call() 2. …

大数据新视界 -- 大数据大厂之 Impala 性能优化:跨数据中心环境下的挑战与对策(上)(27 / 30)

&#x1f496;&#x1f496;&#x1f496;亲爱的朋友们&#xff0c;热烈欢迎你们来到 青云交的博客&#xff01;能与你们在此邂逅&#xff0c;我满心欢喜&#xff0c;深感无比荣幸。在这个瞬息万变的时代&#xff0c;我们每个人都在苦苦追寻一处能让心灵安然栖息的港湾。而 我的…

width设置100vh但出现横向滚动条的问题

在去做flex左右固定,中间自适应宽度的布局时, 发现这样一个问题: 就是我明明是宽度占据整个视口, 但是却多出了横向的滚动条 效果是这样的 把width改成100%,就没有滚动条了 原因: body是有默认样式的, 会有一定的默认边距, 把默认边距清除就是正常的了 同时, 如果把高度设…

百度在下一盘大棋

这两天世界互联网大会在乌镇又召开了。 我看到一条新闻&#xff0c;今年世界互联网大会乌镇峰会发布“2024 年度中国互联网企业创新发展十大典型案例”&#xff0c;百度文心智能体平台入选。 这个智能体平台我最近也有所关注&#xff0c;接下来我就来讲讲它。 百度在下一盘大棋…

探索 RocketMQ:企业级消息中间件的选择与应用

一、关于RocketMQ RocketMQ 是一个高性能、高可靠、可扩展的分布式消息中间件&#xff0c;它是由阿里巴巴开发并贡献给 Apache 软件基金会的一个开源项目。RocketMQ 主要用于处理大规模、高吞吐量、低延迟的消息传递&#xff0c;它是一个轻量级的、功能强大的消息队列系统&…

Android 基于Camera2 API进行摄像机图像预览

前言 近期博主准备编写一个基于Android Camera2的图像采集并编码为h.264的应用&#xff0c;准备分为三个阶段来完成&#xff0c;第一阶段实现Camera2的摄像机预览&#xff0c;第二阶段完成基于MediaCodec H.264编码&#xff0c;第三阶段完成基于MediaCodec H.264解码,针对不同…

QT 线程 QThread QT5.12.3环境 C++实现

一、线程 QT主线程称为GUI线程&#xff0c;负责初始化界面并监听事件循环&#xff0c;并根据事件处理做出界面上的反馈。如果把一些比较复杂或者费时的操作放在主线程中&#xff0c;界面就会出现卡顿或者无响应的现象。一般主线程负责影响界面上的操作&#xff0c; 子线程负责负…

【LLM】一文学会SPPO

博客昵称&#xff1a;沈小农学编程 作者简介&#xff1a;一名在读硕士&#xff0c;定期更新相关算法面试题&#xff0c;欢迎关注小弟&#xff01; PS&#xff1a;哈喽&#xff01;各位CSDN的uu们&#xff0c;我是你的小弟沈小农&#xff0c;希望我的文章能帮助到你。欢迎大家在…

Vue3-后台管理系统

目录 一、完成项目历程 1、构建项目 2、项目的自定义选项 3、 封装组件 4、配置对应页面的路由 5、从后端调接口的方式 二、引入Element Plus、Echarts、国际化组件 1、Element Plus安装 2、Echarts安装 3、国际化 三、介绍项目以及展示 1、项目是基于Vue3、Element …

C0030.Clion中运行提示Process finished with exit code -1073741515 (0xC0000135)解决办法

1.错误提示 2.解决办法 添加环境变量完成之后&#xff0c;重启Clion软件&#xff0c;然后就可以正常调用由mingw编译的opencv库了。

【es6进阶】vue3中的数据劫持的最新实现方案的proxy的详解

vuejs中实现数据的劫持,v2中使用的是Object.defineProperty()来实现的&#xff0c;在大版本v3中彻底重写了这部分&#xff0c;使用了proxy这个数据代理的方式&#xff0c;来修复了v2中对数组和对象的劫持的遗留问题。 proxy是什么 Proxy 用于修改某些操作的默认行为&#xff0…

Python浪漫之画明亮的月亮

目录 1、效果展示 2、完整版代码 1、效果展示 2、完整版代码 import turtledef draw_moon():# 设置画布turtle.bgcolor("black") # 背景颜色为黑色turtle.speed(10) # 设置绘制速度# 绘制月亮的外圈turtle.penup()turtle.goto(0, -100) # 移动到起始…

《线性代数的本质》

之前收藏的一门课&#xff0c;刚好期末复习&#xff0c;顺便看一看哈哈 课程链接&#xff1a;【线性代数的本质】合集-转载于3Blue1Brown官方双语】 向量究竟是什么 线性代数中最基础、最根源的组成部分就是向量&#xff0c;需要先明白什么是向量 不同专业对向量的看法 物理专…

鸿蒙系统ubuntu开发环境搭建

在RISC-V等平台移植鸿蒙系统OpenHarmony&#xff0c;需要使用linux环境进行代码的编译&#xff0c;为兼顾日常办公需要&#xff0c;可采用WindowsUbuntu虚拟机的混合开发的环境&#xff0c;通过网络及文件夹共享&#xff0c;在主机和虚拟机之间共享文件数据。 工具准备&#x…

智能停车解决方案之停车场室内导航系统(二):核心技术与系统架构构建

hello~这里是维小帮&#xff0c;如有项目需求和技术交流欢迎大家私聊我们&#xff01;点击文章最下方获取智慧停车场方案~撒花&#xff01; 随着城市化进程的加速&#xff0c;停车难问题日益凸显。智能停车系统作为缓解停车压力的有效手段&#xff0c;其核心技术与架构的构建至…

(免费送源码)计算机毕业设计原创定制:Java+JSP+HTML+JQUERY+AJAX+MySQL springboot计算机类专业考研学习网站管理系统

摘 要 大数据时代下&#xff0c;数据呈爆炸式地增长。为了迎合信息化时代的潮流和信息化安全的要求&#xff0c;利用互联网服务于其他行业&#xff0c;促进生产&#xff0c;已经是成为一种势不可挡的趋势。在大学生在线计算机类专业考研学习网站管理的要求下&#xff0c;开发一…