【Linux】线程id与互斥(线程三)

上一期我们进行了线程控制的了解与相关操作,但是仍旧有一些问题没有解决在这里插入图片描述
本章第一阶段就是解决tid的问题,第二阶段是进行模拟一个简易线程库(为了加深对于C++库封装linux原生线程的理解),第三阶段就是互斥。

目录

  • 线程id:
    • LWP与tid:
    • 动态库的加载:
    • 线程id:
    • 如何理解维护在库中:
    • 再次感受一下pthread_join():
    • 线程局部存储:
  • 封装线程库:
    • 封装:
  • 互斥:

线程id:

LWP与tid:

我们还是先来写一段简单的代码进行验证一下LWP与线程id的关系。

代码:
在这里插入图片描述
验证结果:在这里插入图片描述

在这里插入图片描述
足以观察到LWP与tid的差距是非常大的。

这说明给用户提供的线程id并不是内核中的LWP,而是自己维护的一个唯一值。
自己就是pthread库。

虽然刚开始觉得不符合常理,但仔细想一想而本该如此:
因为linux并没有线程,但是我们用户需要线程的概念,所以pthread库充当了一个中间角色,封装linux中的轻量级进程,因此,并不需要呈现给用户LWP的值,给用户呈现自己封装的线程id即可。就像C语言中的FILE,我们直接用库封装好的,并不需要在使用文件描述符fd了,也不需要展现给用户。

因为库提供了线程id,所以库也要对pthread进行管理,怎么理解呢?
可以理解为学校给你提供了学号,所以学校要对你进行管理。

我们的linux肯定提供了轻量级进程的调度系统调用,但是一个线程不仅仅需要被调度,也需要一个id,栈大小,被谁启动…这些属性也是由库做管理的!

针对管理我们要展开一下。

动态库的加载:

那就要先看一下线程库的加载,首先动态库和我们的程序肯定都是在磁盘上的文件。
在这里插入图片描述

当我们./运行时,会建立内核数据结构 + 加载数据与代码。

在这里插入图片描述
当我们执行到pthread_create时,因为我们还未加载动态库,会触发缺页中断,去加载动态库,再将动态库映射到共享区。
在这里插入图片描述
此时我们就可以正常去执行我们的pthread_create去创建线程了。

而我们也说过库需要对我们的线程id,栈的大小…进行维护,也就是进行管理

而管理就需要对该对象进行描述再进行组织,下图就是描述他的结构体pthread_t。在这里插入图片描述
其中的struct pthread是用户最基本的线程属性,线程局部存储我们稍后再来进行讲解。而编程栈就是我们常说的每个线程都有一个独立的线程栈!

组织我们可以看成是使用一个数组进行组织起来的。

线程id:

所以以后想找线程属性,拥有地址即可进行管理,而我们的tid就是相应的pthread_t的地址。

如何理解维护在库中:

我们还是以FILE进行举例。
我们的FILE是一个结构体:

struct FILE
{
	int fd;
	char buff[N];
	...
}

我们打开一个文件会得到一个FILE的指针。

而这个FILE结构体指针就维护在标准库中,进入这个函数时,会执行malloc(sizeof(struct FILE))类似的代码在堆上申请空间,等执行完之后返回给用户FILE*,让用户进行操控。所以我们也就可以理解维护在库中了。

就像我们使用STL中的各种容器,不需要管底层是如何扩容的。

再次感受一下pthread_join():

在这里插入图片描述
所以我们也就理解了pthread_create时的attr在这里插入图片描述
这就是用来控制pthread_t的属性,比如控制栈的大小…

总结:linux线程 = pthread库 + LWP,其中内核维护的LWP与动态库中维护的线程是1:1的。

但是同学们,我们该如何保障新线程轻量级进程会使用你指定的栈?
因为我们的轻量级进程中有一个系统调用:clone。
在这里插入图片描述
第一个是回调函数,第二个就是指定的栈,第三个是参数,所以pthread库本质就是对这样的一堆系统调用进行封装。

线程局部存储:

现在还剩最后一个问题,线程局部存储是啥?在这里插入图片描述
我们先来看这样一段代码:
搞一个全局变量,新线程改,主线程读取。
在这里插入图片描述

现象: 一改具改,符合预期。

但是如果我们想要互相不影响,也就是新主线程虽然用同一个全局变量名字,但是实际却是两个地址。
我们可以加入编译选项__thread
注意:这个只可以修饰全局的内置类型。

在这里插入图片描述

现象:可以看到虚拟地址也不相同。
在这里插入图片描述
结论:虽然看起来还是用一个,但实际上各自私有一份

封装线程库:

本质是为了更好的理解C++11是如何进行封装的。

我们的目标是实现如下几个接口:
在这里插入图片描述
也可以在来一个GetStatus,调用可以观察到此线程是否正在运行。

封装:

在这里插入图片描述
先来解释一下成员变量,_name就是线程名字,_tid是线程id,_func是用户传递来的要执行的函数,_isRunning是代表当前线程是否在运行。


在实现时有两个的坑点。
其一:因为我们是进行封装,所以是在类中实现。
但是我们将routine写出了之后却找不到对应routine,这是因为类中的成员默认有隐含的this指针,所以参数的个数就不匹配了。
在这里插入图片描述
解决这种问题的办法很多,但我们最喜欢用static进行修饰,这样就不会有this指针了,这个函数属于整个类。
在这里插入图片描述
可是这样我们就无法访问类中的成员变量了,因为没有this指针~

那怎么办?
把this指针当做参数传给routine!

在这里插入图片描述
这样就可以调用外部给我们传的指定函数了。
可是这样调用未免有些丑陋。
在这里插入图片描述
在将stop,join进行填补即可,注意,我们的构造函数需要一个你指定的名字和待执行函数。

主函数代码:
在这里插入图片描述
现象:

在这里插入图片描述
也是完成了我们预期的工作。

源代码:

#include <iostream>
#include <string>
#include <unistd.h>
#include <cstdio>

namespace cyc
{
    class mythread
    {
    public:
        typedef void (*func_t)(std::string);
        mythread(const std::string &name, func_t func) : _name(name), _func(func), _isRunning(false)
        {
        }
        ~mythread()
        {
        }

        void Excute()
        {
            _isRunning = true;
            _func(_name);
            _isRunning = false;
        }
        static void *routine(void *arg)
        {
            mythread *self = static_cast<mythread *>(arg);
            self->Excute();
            return nullptr;
        }

        void Start()
        {
            int n = ::pthread_create(&_tid, nullptr, routine, (void *)this);
            if (n != 0)
            {
                perror("pthread_create fail");
                exit(1);
            }
        }
        void Stop()
        {
            if (_isRunning)
            {
                pthread_cancel(_tid);
                _isRunning = false;
            }
        }
        void Join()
        {
            int n = pthread_join(_tid, nullptr);
            if (n != 0)
            {
                perror("pthread_join fail");
                exit(1);
            }
        }
        std::string GetStatus()
        {
            if(_isRunning) return "Running";
            else return "sleeping";
        }
    private:
        std::string _name;
        pthread_t _tid;
        func_t _func;
        bool _isRunning;
    };
}

于是我们就完成了一个很简易的封装~

互斥:

多个线程能够看到的资源叫做共享资源。
但是也会有一些问题,比如我们线程通信,一个线程想写hello world,但是刚写了hello就被另一个进程独走了,叫做读写不一致,
所以我们需要对共享资源做保护。

其中最简单的方法是互斥。

但是我们总要先见一见吧。
我们模拟一个抢票的代码,假设一共有1w张票,创建4哥线程同时去抢,每抢一次记录一下抢之前的票数,当票数<=0就是出现了问题。

我们就是用刚刚模拟实现的线程进行操作。
代码:
在这里插入图片描述
现象:
在这里插入图片描述
果然出现了0甚至负数,这就证明我们的抢票提供非常的失败~

可是原理是什么呢?

首先我们要有两个储备知识。
其一是判断也是一种运算,为逻辑运算。
一共有两种运算,分别为算术运算与逻辑运算,简称算罗运算。

其二是线程的切换,CPU内寄存器只有一套,但是拥有的数据有多套。切换时带走自己的数据,回来时会回复!

对于1来说,逻辑判断至少分为3步,虽然语法上表现为3步,但是实际转换到汇编有2-3步。在这里插入图片描述
我们假设线程1在还有最后一张票时进入,已经判断完毕了,但是此时tickets还没进行--,这时时间片到了,线程1被切换,带走了当前寄存器的值与记录执行到的语句。同理2,3,4也都分别执行完判断语句就被切换这就有很大的问题了。

因为只有一张票却有4个线程进入了,等线程a,b,c,d分别恢复时将数据又放回到寄存器中。
注意:进行--时需要将数据重读,修改,在放回内存中这三步

所以票数就变为0,-1,-2…

下章继续~

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

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

相关文章

【解锁未来:深入了解机器学习的核心技术与实际应用】

解锁未来&#xff1a;深入了解机器学习的核心技术与实际应用 &#x1f48e;1.引言&#x1f48e;1.1 什么是机器学习&#xff1f; &#x1f48e;2 机器学习的分类&#x1f48e;3 常用的机器学习算法&#x1f48e;3.1 线性回归&#xff08;Linear Regression&#xff09;&#x1…

【PYG】Planetoid中边存储的格式,为什么打印前十条边用edge_index[:, :10]

edge_index 是 PyTorch Geometric 中常用的表示图边的张量。它通常是一个形状为 [2, num_edges] 的二维张量&#xff0c;其中 num_edges 表示图中边的数量。每一列表示一条边&#xff0c;包含两个节点的索引。 实际上这是COO存储格式&#xff0c;官方文档里也有写&#xff0c;…

爬虫逆向实战(41)-某巢登陆(AES、MD5、RSA、滑块验证码)

一、数据接口分析 主页地址&#xff1a;某巢 1、抓包 通过抓包可以发现在登录时&#xff0c;网站首先请求captcha/querySlideImage/来获取滑块验证码的图片&#xff0c;然后请求captcha/checkCode/接口来验证滑块验证码。滑块验证码校验成功后&#xff0c;请求noshiro/getPu…

高性能LDO电路设计,有配套文档

内容&#xff1a; 1、电路文件&#xff08;有仿真状态&#xff09;和PDK&#xff08;TSMC180&#xff09; 2、配套仿真结果文档讲解6页 3、参考资料三篇 指标&#xff1a; LDO 温度系数1.09ppm LDO 环路增益在 64.3dB&#xff0c;相位裕度在 66&#xff0c;系统稳定。 LDO 最大…

抛弃 Neofetch?众多优秀替代方案等你体验!

目录 抛弃 Neofetch&#xff1f;众多优秀替代方案等你体验Neofetch 的替代品FastfetchscreenFetchmacchina 抛弃 Neofetch&#xff1f;众多优秀替代方案等你体验 NeoFetch 是用 Bash 3.2 编写的命令行系统信息工具&#xff0c;该项目的主要开发人员已将 GitHub 存储库存档&…

【漏洞复现】和丰多媒体信息发布系统 QH.aspx 任意文件上传漏洞

0x01 产品简介 和丰多媒体信息发布系统也称数字标牌&#xff08;Digital Signage&#xff09;&#xff0c;是指通过大屏幕终端显示设备&#xff0c;发布商业、财经和娱乐信息的多媒体专业视听系统&#xff0c;常被称为除纸张媒体、电台、电视、互联网之外的“第五媒体”。该系…

民生银行收大额罚单:信用卡中心一同被罚,管理层如何施救?

民生银行的2024年&#xff0c;再添变故。 近日&#xff0c;国家金融监管总局宁波分局公布了数则行政处罚信息公开表。内容显示&#xff0c;民生银行&#xff08;SH:600016、HK:01988&#xff09;宁波分行、民生银行信用卡中心宁波分中心因多项违法违规事实&#xff0c;共计被处…

【Git 学习笔记】Ch1.1 Git 简介 + Ch1.2 Git 对象

还是绪个言吧 今天整理 GitHub 仓库&#xff0c;无意间翻到了几年前自学 Git 的笔记。要论知识的稳定性&#xff0c;Git 应该能挤进前三——只要仓库还在&#xff0c;理论上当时的所有开发细节都可以追溯出来。正好过段时间会用到 Git&#xff0c;现在整理出来就当温故知新了。…

Redis集群-主从复制、哨兵

●主从复制&#xff1a;主从复制是高可用Redis的基础&#xff0c;哨兵和集群都是在主从复制基础上实现高可用的。 主从复制主要实现了数据的多机备份&#xff0c;以及对于读操作的负载均衡和简单的故障恢复。缺陷&#xff1a;故障 恢复无法自动化&#xff1b;写操作无法负载均…

HCIE实验这样玩太高级了吧?实现FRR+BFD+OSPF与BGP的联动

号主&#xff1a;老杨丨11年资深网络工程师&#xff0c;更多网工提升干货&#xff0c;请关注公众号&#xff1a;网络工程师俱乐部 晚上好&#xff0c;我的网工朋友。 今天搞个HCIE实验玩玩&#xff0c;上回分享了个张总讲解的防火墙配置实验思路&#xff0c;后来还特地搞了个视…

STM32人体心电采集系统

资料下载地址&#xff1a;STM32人体心电采集系统 1、项目功能介绍 此项目主要实现了以STM32为核心的人体心电采集系统软硬件的设计。软件设计过程是在STM32上移植的uCGUI做图形界面&#xff0c;并如实显示采集到的心电波形信号&#xff0c;有SD卡存储和USB数据传输功能。 2、实…

Linux源码阅读笔记08-进程调度API系统调用案例分析

kthread_create_on_node kthread_create_on_node函数功能&#xff1a;指定存储节点创建新内核线程。源码如下&#xff1a; 操作实战 #include <linux/module.h> #include <linux/pid.h> #include <linux/sched.h> #include <linux/kthread.h> #inclu…

复制完若依后,idea没有maven窗口

右击项目 添加框架 添加maven框架就可以了

PV操作经典例题

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、前言&#x1f680;&#x1f680;&#x1f680;二、正文☀️☀️☀️三、总结&#x1f353;&#x1f353;&#x1f353; 一、前言&#x1f680;&#x1f680;&am…

猫头虎 Gemma和Gemini模型的区别是什么?

猫头虎 &#x1f42f; Gemma和Gemini模型的区别是什么&#xff1f; 摘要&#x1f4d8; 在这篇文章中&#xff0c;我们将深入探讨Gemma和Gemini这两个由Google开发的AI模型。我们会对比它们的参数规模、计算资源需求和集成难度&#xff0c;帮助大家了解这两者之间的主要区别。…

【数据结构之B树的讲解】

&#x1f3a5;博主&#xff1a;程序员不想YY啊 &#x1f4ab;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f917;点赞&#x1f388;收藏⭐再看&#x1f4ab;养成习惯 ✨希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出…

万字长文|下一代系统内存数据加速接口SDXI解读

本文内容分为5章节&#xff0c;总计10535字&#xff0c;内容较多&#xff0c;建议先收藏&#xff01; 1.SDXI技术产生的背景 2.SDXI相比DMA的优势 3.SDXI实现原理与架构 3.1 描述符环原理解读 3.2 上下文管理介绍 3.3 AKey与RKey解读 3.4 错误日志和状态管理 3.5 跨Function访…

【PLC】三菱PLC如何和汇川伺服实现485通信

前言 一开始选用的是汇川SV660P脉冲型伺服&#xff0c;由于生产需求需要对伺服的个别参数进行读取和写入操作&#xff0c;但是SV660P并不支持这种情况&#xff0c;因此需要使用485通信来满足。PLC这边选用的是三菱FX5U。 开始 1、首先准备按照下图的引脚提示准备好一根带屏蔽…

昇思25天学习打卡营第6天|简单的深度学习模型实战 - 函数式自动微分

自动微分(Automatic Differentiation)是什么&#xff1f;微分是函数在某一处的导数值&#xff0c;自动微分就是使用计算机程序自动求解函数在某一处的导数值。自动微分可用于计算神经网络反向传播的梯度大小&#xff0c;是机器学习训练中不可或缺的一步。 这些公式难免让人头大…

【C++】红黑树及其实现

目录 一、红黑树的定义1.为什么提出红黑树&#xff1f;2.红黑树的概念3.红黑树的性质 二、红黑树的实现1.红黑树的结构2.红黑树的插入2.1 uncle为红色2.2 uncle为黑色&#xff0c;且是grandfather的右孩子2.3 uncle为黑色&#xff0c;且是grandfather的左孩子 3.红黑树的验证 4…