【Linux】-多线程的知识都收尾(线程池,封装的线程,单例模式,自旋锁)

news2024/7/4 2:34:08

在这里插入图片描述
💖作者:小树苗渴望变成参天大树🎈
🎉作者宣言:认真写好每一篇博客💤
🎊作者gitee:gitee✨
💞作者专栏:C语言,数据结构初阶,Linux,C++ 动态规划算法🎄
如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!

文章目录

  • 前言
  • 一、线程池
  • 二、单例模式
  • 三、STL,智能指针和线程安全
  • 四、其他常见的各种锁
  • 五、总结


前言

今天我们讲解线程的收尾工作,前面博主花了很长时间给大家讲解线程,确实线程这部分要将的东西太多了,大家把前面的掌握好了就不容易了,这篇博主要讲解的是带大家写一个线程池,还有一些c++中带线程,话不多说,我们开始进入正文的讲解。


提示:以下是本篇文章正文内容,下面案例可供参考

一、线程池

大家还记得我写的进程池代码吗??我们当初实现的进程池,是通过父进程创建多个子进程,父进程给子进程派发任务,子进程处理数据,那我们的线程池也是这样去做的,当时线程池比进程池要麻烦一点,他要多数据进行保护,接下来直接看代码,里面有注释:任务还是之前写的计算器任务。
ThreadPool.hpp:

#include<iostream>
#include<pthread.h>
#include<vector>
#include<queue>
using namespace std;

struct ThreadInfo
{
    string threadname;
    pthread_t threadid;
};

template<class T>
class ThreadPool
{
    const static int num=3;//线程信息数组的默认容量,也就是线程数亩
public:
    void lock(pthread_mutex_t* lock)
    {
        pthread_mutex_lock(lock);
    }
    void unlock(pthread_mutex_t* lock)
    {
        pthread_mutex_unlock(lock);
    }
    void wait(pthread_cond_t* cond,pthread_mutex_t*lock)
    {
        pthread_cond_wait(cond,lock);
    }

    void signal(pthread_cond_t* cond)
    {
        pthread_cond_signal(cond);
    }

    string getThreadName(pthread_t tid1)//通过获取线程自己的tid,在线程信息数组找匹配的线程名
    {
        string name;
        for(const auto & tid:threadinfo_)
        {
            if(tid.threadid==tid1)
            {
               return tid.threadname;
            }
        }
        return "None";
    }

    bool IsEmpty()
    {
        return tasks_.empty();
    }
public:
    ThreadPool(int threadcap=num):threadinfo_(num)
    {
        //给互斥锁和条件变量进行初始化
        pthread_mutex_init(&lock_,NULL);
        pthread_cond_init(&cond_,NULL);
    }

   static void* threadfunc(void* arg)//因为线程执行的函数必须是一个参数,不用静态的,就会有一个隐藏的this,但有需要this来调用,所以创建线程的时候就直接将this传进来。
    {
        ThreadPool<T>* tp=static_cast<ThreadPool<T>*>(arg);
        string name=tp->getThreadName(pthread_self());
        while(true)
        {
            tp->lock(&tp->lock_);
            while(tp->IsEmpty())
            {
                tp->wait(&tp->cond_,&tp->lock_);
            }
            T task=tp->pop();
            tp->unlock(&tp->lock_);

            task();//处理任务,线程拿到任务就是自己的,所以不需要在加锁里面。


            cout<<name<<" is working on task: "<<task.GetTask()<<endl;

        }

    }
    void start()//启动线程(创建线程)
    {
        for(int i=0;i<threadinfo_.size();i++)//这个线程数初始化的时候就定好了,所以存放线程信息的数组大小也提前开辟好
        {
            threadinfo_[i].threadname="thread"+to_string(i);
            pthread_create(&(threadinfo_[i].threadid),NULL,threadfunc,this);
        }
    }
    T pop()//获取队列中的任务
    {
        T t=tasks_.front();
        tasks_.pop();
        return t;
    }
   void push(const T& task)
    {
        //加锁的目的让队列只能有一个线程访问,我主线程发任务你其他线程先等着,不然我放一办你就读取,读到的数据不完整
        lock(&lock_);
        tasks_.push(task);
        signal(&cond_);//当主线程发布一个任务后,任务队列肯定不为空,就可以唤醒线程来处理了
        unlock(&lock_);
    }
    ~ThreadPool()
    {
        pthread_mutex_destroy(&lock_);
        pthread_cond_destroy(&cond_);
    }
private:
    vector<ThreadInfo> threadinfo_;//存储创建的每个线程(处理主线程发来任务的线程)信息的数组
    queue<T> tasks_;//存放主线程发来任务的队列

    pthread_mutex_t lock_;//用于处理任务线程直接的互斥和同步
    pthread_cond_t cond_;//条件变量
};

main.cc:

#include"ThreadPool.hpp"
#include<ctime>
#include<unistd.h>
#include"task.hpp"
int main()
{
    cout<<"main start"<<endl;
    ThreadPool<Task>* tp=new ThreadPool<Task>(3);
    srand(time(nullptr)^getpid());
    tp->start();//启动线程池
    while(true)
    {
        int x=rand()%10+1;
        int y=rand()%10;
        char op=opers[rand()%opers.size()];

        Task t(x,y,op);//创建任务

        tp->push(t);//提交任务,其余的处理就让线程池去做吧
        
        sleep(1);
        cout<<"result:"<<t.GetResult()<<endl;
        
    }

    return 0;
}

此线程池一秒往线程里面发布一个任务,而线程需要实现同步互斥,所以结果会看到他们按照顺序来执行主线程发布的任务。
在这里插入图片描述

二、单例模式

之前在C++的博客中写过单例模式

单例模式是一种 "经典的, 常用的, 常考的设计模式.,IT行业这么火, 涌入的人很多. 俗话说林子大了啥鸟都有. 大佬和菜鸡们两极分化的越来越严重. 为了让菜鸡们不太拖大佬的后腿, 于是大佬们针对一些经典的常见的场景, 给定了一些对应的解决方案, 这个就是 设计模式

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

单例通常有两种设计模式,一种是俄汉,一种是懒汉,今天以懒汉为例:

懒汉方式实现单例模式(线程安全版本)
我们将刚才的线程池改成懒汉模式:
ThreadPooldanli.hpp:

#include<iostream>
#include<pthread.h>
#include<vector>
#include<queue>
using namespace std;

struct ThreadInfo
{
    string threadname;
    pthread_t threadid;
};

template<class T>
class ThreadPool
{
    const static int num=3;//线程的默认容量
public:
    void lock(pthread_mutex_t* lock)
    {
        pthread_mutex_lock(lock);
    }
    void unlock(pthread_mutex_t* lock)
    {
        pthread_mutex_unlock(lock);
    }
    void wait(pthread_cond_t* cond,pthread_mutex_t*lock)
    {
        pthread_cond_wait(cond,lock);
    }

    void signal(pthread_cond_t* cond)
    {
        pthread_cond_signal(cond);
    }

    string getThreadName(pthread_t tid1)
    {
        string name;
        for(const auto & tid:threadinfo_)
        {
            if(tid.threadid==tid1)
            {
               return tid.threadname;
            }
        }
        return "None";
    }

    bool IsEmpty()
    {
        return tasks_.empty();
    }
public:
   static void* threadfunc(void* arg)
    {
        ThreadPool<T>* tp=static_cast<ThreadPool<T>*>(arg);
        string name=tp->getThreadName(pthread_self());
        while(true)
        {
            tp->lock(&tp->lock_);
            while(tp->IsEmpty())
            {
                tp->wait(&tp->cond_,&tp->lock_);
            }
            T task=tp->pop();
            tp->unlock(&tp->lock_);
            task();
            cout<<name<<" is working on task: "<<task.GetTask()<<endl;

        }
    }
    void start()//启动线程(创建线程)
    {
        for(int i=0;i<threadinfo_.size();i++)
        {
            threadinfo_[i].threadname="thread"+to_string(i);
            pthread_create(&(threadinfo_[i].threadid),NULL,threadfunc,this);
        }
    }
    T pop()
    {
        T t=tasks_.front();
        tasks_.pop();
        return t;
    }
   void push(const T& task)
    {
        lock(&lock_);
        tasks_.push(task);
        signal(&cond_);
        unlock(&lock_);
    }


   static ThreadPool<T>* getInstance()//对外提供单例对象接口,必须静态的,才能使用类名去调用
    {
        if(nullptr==instance_)//双重检查,第一次申请锁后,创建对象,后面的线程连第一个判断就进不去,就不会每个线程都会有申请锁释放锁的过程,增加效率。
        {
            pthread_mutex_lock(&mutex_);
            if(nullptr==instance_)
            {
                instance_=new ThreadPool<T>();
            }
            pthread_mutex_unlock(&mutex_);
        }
        return instance_;
    }

   //防拷贝
    ThreadPool(const ThreadPool&)=delete;
    const ThreadPool& operator=(const ThreadPool&)=delete;
private://将构造函数私有化,就创建不了对象了。
     ThreadPool(int threadcap=num):threadinfo_(num)
    {
        //给互斥锁和条件变量进行初始化
        pthread_mutex_init(&lock_,NULL);
        pthread_cond_init(&cond_,NULL);
    }
     ~ThreadPool()
    {
        pthread_mutex_destroy(&lock_);
        pthread_cond_destroy(&cond_);
    }
private:
    vector<ThreadInfo> threadinfo_;//存储每个线程信息的数组
    queue<T> tasks_;//存放主线程发来任务的队列

    pthread_mutex_t lock_;//互斥锁
    pthread_cond_t cond_;//条件变量
    
    static ThreadPool<T>* instance_;//定一个懒汉的单例对象,不能是栈区的变量,会套娃
    static pthread_mutex_t mutex_;//防止多线程同时创建对象,在刚判断完为不为空的时候,被切走,下次进来直接创建对象,这样就不止一个对象了
};

template<class T>
ThreadPool<T>* ThreadPool<T>::instance_=nullptr;//给单利对象进行初始化

template<class T>
pthread_mutex_t ThreadPool<T>::mutex_=PTHREAD_MUTEX_INITIALIZER;//初始化互斥锁

maindanli.cc:

#include"ThreadPooldanli.hpp"
#include<ctime>
#include<unistd.h>
#include"task.hpp"
int main()
{
    cout<<"main start"<<endl;
    
    srand(time(nullptr)^getpid());
    ThreadPool<Task>::getInstance()->start();//启动线程池
    while(true)
    {
        int x=rand()%10+1;
        int y=rand()%10;
        char op=opers[rand()%opers.size()];

        Task t(x,y,op);//创建任务

         ThreadPool<Task>::getInstance()->push(t);//提交任务
            sleep(1);
        cout<<"result:"<<t.GetResult()<<endl;
        
    }

    return 0;
}

效果和刚才的一样的。
最重要的代码:

 static ThreadPool<T>* getInstance()//对外提供单例对象接口,必须静态的,才能使用类名去调用
    {
        if(nullptr==instance_)//双重检查,第一次申请锁后,创建对象,后面的线程连第一个判断就进不去,就不会每个线程都会有申请锁释放锁的过程,增加效率。
        {
            pthread_mutex_lock(&mutex_);
            if(nullptr==instance_)
            {
                instance_=new ThreadPool<T>();
            }
            pthread_mutex_unlock(&mutex_);
        }
        return instance_;
    }

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

STL中的容器是否是线程安全的?
不是.
原因是, STL 的设计初衷是将性能挖掘到极致, 而一旦涉及到加锁保证线程安全, 会对性能造成巨大的影响.而且对于不同的容器, 加锁方式的不同, 性能可能也不同(例如hash表的锁表和锁桶).因此 STL 默认不是线程安全. 如果需要在多线程环境下使用, 往往需要调用者自行保证线程安全.
智能指针是否是线程安全的?
对于 unique_ptr, 由于只是在当前代码块范围内生效, 因此不涉及线程安全问题.对于 shared_ptr, 多个对象需要共用一个引用计数变量, 所以会存在线程安全问题. 但是标准库实现的时候考虑到了这个问题, 基于原子操作(CAS)的方式保证 shared_ptr 能够高效, 原子的操作引用计数/

四、其他常见的各种锁

悲观锁:在每次取数据时,总是担心数据会被其他线程修改,所以会在取数据前先加锁(读锁,写锁,行锁等),当其他线程想要访问数据时,被阻塞挂起。
乐观锁:每次取数据时候,总是乐观的认为数据不会被其他线程修改,因此不上锁。但是在更新数据前,会判断其他数据在更新前有没有对数据进行修改。主要采用两种方式:版本号机制和CAS操作。
CAS操作:当需要更新数据时,判断当前内存值和之前取得的值是否相等。如果相等则用新值更新。若不等则失败,失败则重试,一般是一个自旋的过程,即不断重试。
自旋锁: 自旋锁适用于访问临界区时间短的。我们之前使用的锁只要申请失败就被挂起了,等释放后,唤醒在去竞争锁,挂起于欧唤醒浪费时间,自旋锁是一只申请,申请失败还申请,直到申请成功,当一个线程拿到锁,访问临界资源的时间过长,还不如让他挂起,频繁去申请锁不好,所以自旋锁适用于访问临界资源时间少的场景。(trylock函数)
在这里插入图片描述
这两个就是自旋锁加锁的方式,第一个是阻塞,第二个是非阻塞,为什么还阻塞呢?原因是他申请不到锁会一直申请,在用户看来是被阻塞住了,非阻塞的意思是,申请失败就返回,没有阻塞效果。

五、总结

到这里我们的多线程部分就讲解到这里,也宣告我们系统部分就讲解到这里了,后面博主贵更新网络相关的知识,就是可以通过网络来获取数据来,让代码变得好玩起来了,我们这票就到这里了,我们下篇再见。

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

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

相关文章

Unity使用反向遮罩实现镂空shader

实现步骤&#xff1a; 1&#xff0c;创建两个材质球&#xff0c;遮罩层的属性如下&#xff1a; 被遮罩层的属性如下&#xff1a; 2&#xff0c;使用两张image&#xff0c;遮罩层在父节点&#xff0c;被遮罩层在子节点&#xff0c;然后分别添加材质球与镂空图片 实现效果如下&a…

无人零售模式下,“IoT+鸿蒙”实现零代码搭建自动售货机监控大屏的可能性摸索

前言 新零售模式下&#xff0c;对loT的探索与应用还在继续。 而数字时代&#xff0c;数字化转型在零售行业中蔓延&#xff0c;而对于新的消费方式的探索&#xff0c;也在如火如荼的进行中。于是&#xff0c;一种新零售的形式——无人零售逐渐形成概念。 如果说&#xff0c;人…

D78XX系列——用于各种电视机、收录机、电子仪器、设备的稳压电源电路,输出电流大,内设过热、短路保护电路,无需外接元件

D78XX系列是用于各种电视机、收录机、电子仪器、设备的稳压电源电路。包括D7805、D7806、 D7808、 D7809、 D7810、 D7812、 D7815。 主要特点&#xff1a; ● 输出电流大&#xff0c;IOMAX 1A. ● 无需外接元件。 ● 内设过热、短路保护电路 ● 封装形式: T0-220

Cmake语法学习2:常用变量

目录 1.常用变量简介 1.1提供信息的变量 1.2改变行为的变量 1.3描述系统的变量 ​编辑1.4控制编译的变量 2.提供信息的变量 2.1PROJECT_SOURCE_DIR 和 PROJECT_BINARY_DIR 2.2 CMAKE_SOURCE_DIR 和 CMAKE_BINARY_DIR 2.3CMAKE_CURRENT_SOURCE_DIR 和CMAKE_CURRENT_BIN…

如何用AI人工智能写作?6个AI写作神器推荐

在日常生活中&#xff0c;我们往往会遇到一些关于写作方面的难题&#xff0c;毕竟传统的写作方式还是会存在一些局限性&#xff0c;幸运的是&#xff0c;随着人工智能技术的不断发展&#xff0c;AI写作已经成为了现实。AI写作神器可以帮助我们提高写作效率&#xff0c;解决写作…

骨传导耳机的技术原理是什么?和传统耳机相比有哪些优点?

骨传导耳机通过人体骨骼来传递声音&#xff0c;可以绕过耳道和耳膜直接传达音频到听者的内耳&#xff0c;开放双耳的佩戴方式可以在享受音乐或通话的同时保持对周围环境的感知&#xff0c;这种设计在户外活动或运动等场景下的使用尤为实用&#xff0c;可以避免堵塞耳朵&#xf…

RK3568驱动指南|驱动基础进阶篇-进阶1 编译进内核的驱动系统是如何运行的?

瑞芯微RK3568芯片是一款定位中高端的通用型SOC&#xff0c;采用22nm制程工艺&#xff0c;搭载一颗四核Cortex-A55处理器和Mali G52 2EE 图形处理器。RK3568 支持4K 解码和 1080P 编码&#xff0c;支持SATA/PCIE/USB3.0 外围接口。RK3568内置独立NPU&#xff0c;可用于轻量级人工…

Python实现排序算法

目录 一&#xff1a;快速排序 二&#xff1a;合并排序 三&#xff1a;冒泡排序 四&#xff1a;插入排序 五&#xff1a;选择排序 一&#xff1a;快速排序 def quicksort(arr): if len(arr) < 1: return arr pivot arr[len(arr) // 2] le…

Unity | 渡鸦避难所-9 | 角色名字及血条等信息

1 效果预览 游戏中角色的名字和血条是非常重要的元素&#xff0c;它们可以帮助玩家了解角色的身份和状态。在 Unity 中&#xff0c;可以使用 UGUI 来实现这些功能 2 实现方案 1 画布 (Canvas) 画布 (Canvas) 组件表示进行 UI 布局和渲染的抽象空间。所有 UI 元素都必须是附加…

UE5 虚幻游戏报错常用解决方法(幻兽帕鲁UE5报错)

在体验使用虚幻引擎5、4&#xff08;UE5/UE4&#xff09;开发的游戏如《幻兽帕鲁》时&#xff0c;玩家可能会遇到各种报错情况&#xff0c;例如黑屏、闪退、C运行时错误等。本博客将汇集一系列有效解决方案&#xff0c;通过调整虚幻引擎内置命令行参数以及优化系统环境&#xf…

环状热力图R语言画法

环状热力图&#xff08;Circular Heatmap&#xff09;是一种以环状布局展示数据的可视化方法。它结合了热力图和极坐标系统&#xff0c;能够有效地显示数据的关系、模式和趋势。 环状热力图通常用于可视化二维数据矩阵&#xff0c;其中行和列代表不同的类别或变量&#xff0c;…

“欢天喜地迎新春”下姜村邻里守望写对联活动

卯兔追冬去&#xff0c;辰龙报春来。空谷幽香谱佳期&#xff0c;红联金句寄吉祥。春联是我国特有的文学形式&#xff0c;贴春联是继承传统习俗的一种方式&#xff0c;是对祖先的尊敬&#xff0c;对传统的继承。春节前夕&#xff0c;家家户户贴上红红的春联&#xff0c;一副副透…

前妻(C#)-基础03-枚举-预处理指令

前妻C#-基础语法03 枚举关于控制台IO及注释C#预处理指令 枚举 枚举是用户定义的整数类型。在声明一个枚举时&#xff0c;要指定改枚举的实例可以包含的一组可接受的值。不仅如此&#xff0c;还可以给值指定易于记忆的名称&#xff0c;如果在代码的某个地方&#xff0c;要试图把…

【SpringCloud】使用OpenFeign进行微服务化改造

目录 一、需求与背景二、OpenFeign 远程调用技术原理三、项目代码演示3.1 引入依赖3.2 实现OpenFeign注解修饰接口3.3 指定 OpenFeign 远程调用接口的扫描路径 四、OpenFeign 在日志中打印Request和Response五、OpenFeign 客户端超时配置六、使用 OpenFeign 实现服务降级6.1 实…

QT SQL

QT SQL模块提供数据库编程的支持&#xff0c;支持多种常见的数据库&#xff1a;MySQL\Oracle\MS SQL Server\SQLite等。SQL模块包含多个类&#xff0c;可以实现&#xff1a;数据库连接、SQL语句执行、数据获取与界面显示 等功能。数据 与 界面间用Model\View架构。 一、 二、Q…

禅道列表页编辑页添加页自定义字段

1&#xff0c;数据库表 zt_story 添加自定义字段 bakDate1&#xff0c;bakDate2&#xff0c;bakDate3&#xff0c;bakDate4 2&#xff0c;在 /opt/lampp/htdocs/zentaopms/extension/custom/story/ext/config 中添加bakDate.php文件 <?php $config->story->datatab…

【2024美国大学生数学建模竞赛】2024美赛C题网球运动中的势头,网球教练4.0没人比我更懂这个题了!!!

【2023美国大学生数学建模竞赛】2024美赛C题 问题分析、数学模型、实现代码、完整论文 引言 本人是计算机博士&#xff0c;拥有10年网球球龄&#xff0c;2023年的温网决赛&#xff0c;熬夜到半夜全称观看完了直播&#xff0c;对于网球规则、比赛的数据非常熟悉&#xff0c;这个…

【代码随想录-链表】环形链表 II

💝💝💝欢迎来到我的博客,很高兴能够在这里和您见面!希望您在这里可以感受到一份轻松愉快的氛围,不仅可以获得有趣的内容和知识,也可以畅所欲言、分享您的想法和见解。 推荐:kwan 的首页,持续学习,不断总结,共同进步,活到老学到老导航 檀越剑指大厂系列:全面总结 jav…

windows下使用verdaccio构建npm私服环境

一.背景 npm太慢了&#xff0c;每次jenkins自动构建等太久&#xff0c;我虽然是后端coder&#xff0c;也看不惯。本文目的只是说明怎么搭建npm私服&#xff0c;我现在只想构建快一点。所以&#xff0c;暂时没有考虑多个开发者将自定义组件上传到私库并共享的问题&#xff0c;以…

Linux中判断文件系统的方法

文章目录 Linux中判断文件系统的方法1.使用mount命令2.使用blkid命令3.使用file命令4.使用fstab文件5.使用df命令&#xff08;这个用的比较多&#xff09;6.使用fsck命令7.使用lsblk命令(推荐-简单好用) Linux中判断文件系统的方法 1.使用mount命令 # 这样查看的只有已经挂载…