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

在这里插入图片描述
💖作者:小树苗渴望变成参天大树🎈
🎉作者宣言:认真写好每一篇博客💤
🎊作者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.mfbz.cn/a/365004.html

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

相关文章

深度剖析Sentinel热点规则

欢迎来到我的博客&#xff0c;代码的世界里&#xff0c;每一行都是一个故事 深度剖析Sentinel热点规则 前言核心概念解析&#xff1a;数字守护者的起源核心概念解析&#xff1a;简单示例演示&#xff1a; 参数索引&#xff1a;规则的基石参数索引的作用&#xff1a;不同场景下选…

2024美国大学生数学建模美赛选题建议+初步分析

总的来说&#xff0c;去年算是美赛环境题元年&#xff0c;去年的开放度是较高的&#xff0c;今年每种赛题类型相对而言平均了起来 提示&#xff1a;DS C君认为的难度&#xff1a;E<BCF<AD&#xff0c;开放度&#xff1a;DBCE<A<F。 以下为A-F题选题建议及初步分析…

小型内衣裤洗衣机哪个牌子好?家用小型洗衣机推荐

相信对于很多用户而言&#xff0c;宁愿强撑着疲惫的身子手洗内衣裤&#xff0c;也不愿把内衣裤与外穿衣物一起放进洗衣机洗。内衣裤与外穿衣物的脏污情况不同&#xff0c;内衣裤是贴身衣物&#xff0c;上面留有人体的汗液和分泌物&#xff0c;有可能带有大量真菌。而外衣上则是…

动环系统断电告警的防误报

机房一般接入的市电为三相380伏特&#xff0c;也有用单向220伏特的。UPS本身提供断电告警的功能&#xff0c;这个告警在各种种类的UPS中都是提供的&#xff0c;不同电压的市电输入都支持&#xff1b;三相电另外有缺相告警事件。但这些告警事件存在抖动或者误判。 瞬间的低压或…

LiveGBS流媒体平台GB/T28181功能-支持配置开启 HTTPS 服务什么时候需要开启HTTPS服务

LiveGBS功能支持配置开启 HTTPS 服务什么时候需要开启HTTPS服务 1、配置开启HTTPS1.1、准备https证书1.1.1、选择Nginx类型证书下载 1.2、配置 LiveCMS 开启 HTTPS1.2.1 web页面配置1.2.2 配置文件配置 2、验证HTTPS服务3、为什么要开启HTTPS3.1、安全性要求3.2、功能需求 4、搭…

Linux基础知识合集

整理了一下学习的一些关于Linux的一些基础知识&#xff0c;同学们也可以通过公众号菜单栏查看&#xff01; 一、基础知识 Linux基础知识 Linux命令行基础学习 Linux用户与组概念初识 Linux文件与目录权限基础 Linux中文件内容的查看 Linux系统之计划任务管理 二、服务器管理 Vm…

Vue中使用 Element-ui form和 el-dialog 进行自定义表单校验清除表单状态

文章目录 问题分析 问题 在使用 Element-ui el-form 和 el-dialog 进行自定义表单校验时&#xff0c;出现点击编辑按钮之后再带年纪新增按钮&#xff0c;出现如下情况&#xff0c;新增弹出表单进行了一次表单验证&#xff0c;而这时不应该要表单验证的 分析 在寻找多种解决…

深信服技术认证“SCCA-C”划重点:深信服云计算关键技术

为帮助大家更加系统化地学习云计算知识&#xff0c;高效通过云计算工程师认证&#xff0c;深信服特推出“SCCA-C认证备考秘笈”&#xff0c;共十期内容。“考试重点”内容框架&#xff0c;帮助大家快速get重点知识。 划重点来啦 *点击图片放大展示 深信服云计算认证&#xff08…

【无刷电机学习】电流采样电路硬件方案

【仅作自学记录&#xff0c;不出于任何商业目的】 目录 AD8210 INA282 INA240 INA199 AD8210 【AD8210数据手册】 在典型应用中&#xff0c;AD8210放大由负载电流通过分流电阻产生的小差分输入电压。AD8210抑制高共模电压(高达65V)&#xff0c;并提供接地参考缓冲输出&…

Trie树数据结构——(字符串统计,最大异或对)

Trie树&#xff1a;是一种能够高效存储和查找字符串集合的数据结构 Trie字符串统计 思路&#xff1a; &#xff08;笔记来自AcWing 835. Trie字符串统计 - AcWing&#xff09; 代码如下&#xff1a; #include<iostream> #include<cstdio> #include<string>…

华为FreeClip耳机可以调节音量大小吗?附教程!

不会只有我一个人吧&#xff1f;都用华为FreeClip耳机一段时间了&#xff0c;才发现它竟然不支持在耳机上直接调节音量&#xff0c;也是没谁了&#xff01;但是后来自己摸索了一下&#xff0c;发现了华为FreeClip耳机原来是几个简单有效的调节音量大小的方法滴~不得不说&#x…

计算机视觉:高级图像处理,满足您的所有需求。

一、说明 特征提取是机器学习管道中的关键步骤&#xff0c;可增强模型在不同数据集上的泛化和良好表现能力。特征提取方法的选择取决于数据的特征和机器学习任务的具体要求。本文揭示图像处理的数学原理&#xff0c;实现增强的计算机视觉 二、关于计算机视觉的普遍问题 在计算机…

Nginx 多项目部署,vue刷新404 解决方案

网上找的资料大多都解决不了&#xff0c;废话不多说直接告诉你解决方法。 环境是 TP6 VUE前端官网 VUE 后台管理 部署 两个项目 刷新 404 解决方案 Nginx 配置 直接贴图 如果解决了&#xff0c;给我顶起来&#xff0c;让更多人 快速的解决。

借力华为云CodeArts,使用软件开发生产线快速搭建项目

前言 项目的实际开发&#xff0c;研发接到需求并不是立马进入开发的&#xff0c;实际的开发生成流程是一个完整的迭代流程。 流程的节点和每个节点的内容如下&#xff1a; 开发生产的流程很标准很规范&#xff0c;看似研发只需要按照流程执行每一步的操作即可。但实际开发中&…

2024美赛数学建模E题思路分析 - 财产保险的可持续性

1 赛题 问题E&#xff1a;财产保险的可持续性 极端天气事件正成为财产所有者和保险公司面临的危机。“近年来&#xff0c;世界已经遭受了1000多起极端天气事件造成的超过1万亿美元的损失”。[1]2022年&#xff0c;保险业的自然灾害索赔人数“比30年的平均水平增加了115%”。[…

react 之 UseReducer

UseReducer作用: 让 React 管理多个相对关联的状态数据 import { useReducer } from react// 1. 定义reducer函数&#xff0c;根据不同的action返回不同的新状态 function reducer(state, action) {switch (action.type) {case INC:return state 1case DEC:return state - 1de…

实验二 DES密码算法的设计与实现

✅作者简介&#xff1a;CSDN内容合伙人、信息安全专业在校大学生&#x1f3c6; &#x1f525;系列专栏 &#xff1a;简单外包单 &#x1f4c3;新人博主 &#xff1a;欢迎点赞收藏关注&#xff0c;会回访&#xff01; &#x1f4ac;舞台再大&#xff0c;你不上台&#xff0c;永远…

Git版本管理工具(实战进阶):零基础到起飞实战项目完整篇 →Git学习一篇就够 从基本指令、到本地仓库、远程仓库、实战项目开发演练介绍超详细!

heima 李师傅最新版 Git的讲解 文章目录 Git在实战项目开发使用功能学习01.Git 初识02.Git 仓库03.Git 的三个区域04.Git 文件状态05.Git 暂存区作用06.练习-登录页面07.Git-切换版本08.删除文件09.忽略文件10.分支的概念11.练习-登录 bug 修复12.分支-合并与删除13.分支-合并与…

LeetCode--189

189. 轮转数组 提示 给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右轮转 1 步: [7,1,2,3,4,5,6] 向右轮转 2 步: [6,7,1,2,3,4,5] 向右轮转…

Leetcode 热门百题斩(第一天)

介绍 针对leetcode的热门一百题&#xff0c;解决大多数实习生面试的基本算法题。通过我自己的思路和多种方法&#xff0c;供大家参考。 1.两数之和&#xff08;题号&#xff1a;1) 方法一 最先想到的就是两个for去遍历匹配。 class Solution {public int[] twoSum(int[]…