linux线程 | 线程的概念

        前言:本篇讲述linux里面线程的相关概念。 线程在我们的教材中的定义通常是这样的——线程是进程的一个执行分支。 线程的执行粒度, 要比进程要细。 我们在读完这句话后其实并不能很好的理解什么是线程。 所以, 本节内容博主将会带友友们理解什么是线程!线程和进程的关系等等。现在, 开始我们的学习吧!

        ps:本节适合已经学习了进程的友友们进行观看哦。

目录

linux中的线程该如何理解

linux实现线程的方案

重新定义进程和线程

tcb

模拟线程

tcb与模拟线程的区别

如何分配线程

线程和进程的切换问题


linux中的线程该如何理解

         首先我们知道我们的进程看待自己所能看到的所有资源都是通过地址空间来看的。 所以,地址空间是进程的资源窗口。所以我们的进程如果想做任何事情, 诸如加载动态库, 申请内存, 查看变量等等操作就必须使用地址空间加页表的方案, 从物理内存当中找到我们的代码和数据或者堆空间 ——地址空间是进程的资源窗口。 如果我们今天cpu调度进程时, 就相当于task_struct在cpu所对应的运行队列中去排队。 

        另外, 在我们的子进程当中, 我们的子进程是拷贝一份父进程的task_struct和父进程的地址空间。 然后重新映射到物理空间的不同位置。 如同下图:

        但是呢, 如果我们这么设计: 不再拷贝进程的PCB以及地址空间了。 只拷贝PCB, 然后让拷贝的PCB分走一部分进程地址空间的代码区, 堆区, 等等各种区。 然后呢, 创建多个PCB, 这些PCB都和原本的PCB共享一个地址空间。 (如何划分这个地址空间成为若干份, 由后续的设计者规定)。 那么我们就会发现我们新创建的进程以及曾经的进程, 他们可以对地址空间进行一定程度上的分享, 此时我们就可以认为新创建出来的这些新进程, 他们执行的粒度要比我们原本的进程要细一些。 并且, 又因为这些新进程执行的代码是一整个一整个代码区的一部分, 所以就称为它是一个分支, 我们为了区分这种进程, 就叫做线程。 

linux实现线程的方案

        在linux中, 线程在进程的地址空间运行(为什么?任何执行流要执行, 必须要有资源!而地址空间是进程的资源窗口。又因为线程是进程内部的一个执行流, 所以线程在进程的地址空间运行!)

        在linux中, 线程的执行力度要比进程更细? 这是因为线程执行进程代码的一部分。

不同的操作系统对于线程的概念一定都是一样的, 但是不同的操作系统对于线程的管理或者线程的整体的实现方案不一样的。 linux的实现方案是上面的样子。 所以, 站在cpu的角度, cpu知不知道我们现在正在访问task_struct是线程还是进程呢? 或者说cpu需不需要知道呢?——答案是不需要关心cpu只有调度执行流的概念, 所以cpu只要访问代码就访问代码, 访问数据就访问数据。 cpu只要拿到代码和数据, 并不关心到底是进程还是线程。 

重新定义进程和线程

  •         线程:我们认为,线程是操作系统调度的基本单位!!
  •         进程:进程是承担分配系统资源的基本实体!

        (我们之前的理解是进程 = 内核数据结构task_struct+代码和数据 ------- v1)现在我们把整个的所有的线程, 以及地址空间, 页表和页表映射到的一点点物理内存叫做进程。 就如同下图:

        在操作系统中, 我们分配资源的方式是以进程为单位进行分配的。 就比如线程, 那么操作系统创建task_struct, 然后分配一部分地址空间——执行流是资源吗? 执行流就是task_struct, 就是资源。 

        所以,进程和线程的关系就是:进程里面就是包含线程的。 因为进程是操作系统分配资源的基本实体, 而我们的线程是进程内部的执行流资源。 

        那么, 我们重新理解进程, 如何理解以前的进程呢?

        操作系统以进程为单位为我们分配资源。 只不过我们当前的进程内部, 只有一个执行流!!——也就是说, 我们之前讲的才是特殊情况。 现在的多个线程是正常情况。 

        如果我们的一个进程内部真的有多个进程。 所以, 进程与线程的比率一定是1 : n的, 并且至少为1 : 1。 所以, 当进程执行的时候, 当前进程的状态是什么, 这个线程当前执行到什么位置了当前需要访问哪些资源, 期间要访问哪些资源。 这个线程是属于哪个进程的, 这个线程需要被切换吗, 什么时候被切换等等这些问题。那么我们知道, 线程一定比进程更多。 线程不是一创建就直接退出了。 也不是一创建就完成了。 是创建的时候就开始了, 操作系统要调用这个线程, 运行这个线程, 切换这个线程。 但是, 我们知道, 线程的个数是非常多的。 那么这么多的线程 , 这么多的操作需要执行。 所以操作系统要进行调度, 就一定需要管理起来。 而如何管理呢? 就是先描述再组织!

tcb

        对于大部分操作系统来说, 就是描述struct tcb。 就比如windows。 对于线程来说, 线程是属于一个进程的。 而一个进程的线程有很多, 有需要组织起来, 那么我们操作系统就需要描述一个数据结构组织线程, 然后将这个数据结构和一个进程组织起来, 还要和调度队列联系起来。 如果我们的线程出现问题, 那么又会影响我们的进程, 影响我们的调度队列等等。 这就太复杂了。 但是呢, 又不得不这么干, 所以windows就这么干了, 也就是struct tcb; 

模拟线程

        对于计算机世界来说, 我们要管理线程, 那么就势必要先描述在组织。 但是linux程序员们并不想windows程序员们一样重新描述和组织线程。 没有人规定必须用新的描述方式和组织方式管理线程。 linux的设计者们考虑到既然线程也有上下文, 也有各种状态。 进程task_struct也有, 所以就没有必要再单独为进程重新描述,重新组织了。 所以linux的设计者们就直接服用进程数据结构和管理算法了。 ——struct task_struct——模拟线程。 

        那么这样做之后, 如何区分线程, 进程呢?——如果我们的数据结构内部有一份资源, 那么就是进程。 如果里面有多个PCB, 那么这个整体就是线程。 而且未来我们不区分这个PCB是不是进程, 是不是线程, 我们统一叫做执行流。 而什么叫做进程呢?页表、 地址空间、 所有的线程、 一点点物理地址这些合起来才是进程(分配资源的基本实体), PCB就是执行流, 只不过以前进程只有一个PCB, 也就是只有一个执行流, 而现在线程有多个PCB, 多个执行流。

上面的tcb, 和模拟线程是两钟描述线程的方案。 但是对于模拟线程这个方案, 因为进程和线程是类似的。 所以线程如果再重新描述一次就势必会造成程序的复杂, 当出现问题的时候就不好处理, 但是如果使用模拟线程的方法, 程序就会更加简洁。 出现的问题一定会更少, 维护成本降低, 所以他的健壮性一定要比所有的操作系统要强, 而且强的不是一丁半点。 

tcb与模拟线程的区别

        无论是使用方案1还是方案2,, 都是具体的实现方案。 那么说linux中没有真正意义上的线程, 不是真的说linux没有线程。 对于tcb来说, tcb是将线程的概念体现在了代码上面。 而我们的linux并没有将线程的概念体现在代码上面, 而是体现在了人们的大脑当中!我们的windows通过tcb遵守了线程的概念。 我们的linux也通过模拟线程遵守了线程的概念!!并且, 我们是使用进程的内核数据结构模拟的线程。 

        操作系统, 什么叫做操作系统, 我们在学校上的课, 实际上是规定一款操作系统应该是什么样子。 或者说一款操作系统在设计上应该符合什么概念。 它是一款操作系统设计的指导手册, 但是具体我们在实现的时候, 不同的操作系统有不同的方案。

        题外话:我们的cpu在执行的时候, 当它看到一个pcb的时候, 执行一部分代码时, cpu执行的代码, 是进程的代码, 还是线程的代码呢? 其实cpu无法区分到底是进程还是线程。 但是站在上帝的视角我们知道, cpu执行的执行流, 一定是小于等于进程的。 在大部分OS中, 执行流小于进程就是线程, 等于就是进程(线程的粒度更细)。 在linux中的执行流被称为“轻量级执行流”。

        在我们国家, 承担分配社会资源的基本实体是什么呢?——家庭。 自古到今, 我们的社会都是以家庭为单位来向社会进行资源申请的。 然后呢, 我们就会发现, 在我们的家庭里:我们的爷爷奶奶每天就是打太极拳, 逛公园, 或者看别的老人下棋啥的, 目的就是把自己的身体养好。 我们的爸爸妈妈呢, 每天就是工作, 赚钱养家。 小孩在家就是学习。 那么我们会看到, 在这个家庭里面, 每一个人天然的就会存在一种小人物。 但是, 不管每一个人有着怎么样的小任务, 他们一定会有一个共同的任务——把自己家的日子过好。 只不过每一个人为了把日子过好, 领取的任务是不同的。 所以我们把家庭整体叫做进程, 把里面的爸爸妈妈, 爷爷奶奶, 我们自己叫做一个线程。 每一个线程和每一个进程的关系就是:线程是在进程的内部进行, 而且每一个线程能够做什么, 和我们进程所拥有的资源是有关的!! 而如果这个家庭里只有一个人, 就是我们进程的情况!!

如何分配线程

        如何分配线程, 这个就需要用到我们页表的知识。 我们要先谈页表结构, 页表的工作原理。 再来谈如何分配线程, 看下面一张图:

        当我们的程序加载到物理内存的时候, 页表有映射,那么从物理内存当中读取到cpu里面的地址是什么呢? 读到的是虚拟地址cpu读到虚拟地址后, cpu再从地址空间找到对应的位置, 通过页表映射就能够执行这个进程了。 

        那么, 我们以32位为例, 谈一谈虚拟地址是如何转化到物理地址的。 如果在32位条件下虚拟地址有多少位的呢? 答案也是32位。  那么, 首先我们对应的虚拟地址有32位, 并且这里需要知道的是这32位虚拟地址不是一个整体。 这32位虚拟地址我们把它转化为了10 + 10 + 12。 

        而且, 页表不是一整块的。 如果页表是一整块的, 我们知道, 页表中的条目最多2 ^ 32个, 如果是整块的, 那么我们一个条目假设有10字节那么我们的内存就爆了, 所以, 页表一定不是一整块的。 页表是分为三个板块, 其中有一级页表、二级页表、还有一个页框级别页表。 就如同下图:

        什么意思呢, 意思就是说, 我们未来从cpu内读到的某个虚拟地址, 这个虚拟地址一共有32位。 假如这个虚拟地址是1000 0000 1000 0010 0100 1000 0010 0010. 那么它其实存放的规则是按照10 + 10 + 12来存放的。 如下:

       

        并且把他们分成第一个部分, 第二个部分, 第三个部分。所以, 将来会用我们第一个部分查找我们的第一季页表。 用第二部分查找我们的第二级页表。 第三部分, 查找我们的最后一个页表。 而查找的过程就是先将对应的各个部分转化为十进程。 再由十进制找到三个页表中的下标索引。 而我们的页表的前20位, 对应的就是先查第一级页表, 再查第二级页表。 查完后, 就已经能够查找到我们的物理内存对应的页框了!!

        然后, 我们的第三个表, 指向的就是页框内具体的偏移量。 也就是说, 我们通过二级页表查找到了具体的哪一个页框, 然后得到了这个页框的地址, 假如是0x0012ff40。 那么再通过最后12位来获取偏移量。 最后0x0012ff40 + 虚拟地址最后12位。 就能得到最终的结果。 

        那么我们思考一下, 这个时候的页框, 最大是多少呢? 

我们知道我们的页表当中也有一些权限字段, 是可读还是可写等等。 一共会有1024个二级页表。 假设我们一个二级页表项是4个字节。 所以一个二级页表4 * 1024byte, 即4kb。 所以将来一个页表是可以放到一个页框里面的。 那么我们一个进程, 最多会有多少4kb呢? 因为页目录最多1024个, 所以我们最终要乘以1024. 即最多会有4 * 1024kb = 4MB。 那么4MB大不大呢? 这个空间挺大的, 但是这个空间和上面的虚拟地址空间比起来已经很小很小了。 而且, 我们一个进程, 会把整个地址空间用完吗? 不会的。另外, 我们的内核地址空间, 是不需要给每个进程都维护一份的。 并且, 我们的每个进程不一定把每个地址空间全部弄完。 所以, 大部分进程根本就不能把第一个一级页表全部用完。 所以我们想说的是, 二级页表不一定全部存在。 (在大部分情况下都是不全的。但是, 其实创建一个进程依旧是一个很“重”的工作。)

        那么看这个问题, 这里有int a = 10; 按道理来说, int类型有4字节, 那么就有4个地址。 但是为什么&a只拿到了一个地址呢? 这是因为我们有类型的存在。 我们的int类型, 只需要知道首地址, 然后之后的4个字节就按照偏移量向后找就行。 c/c++里面的自定义的类,归根结底就是我们一大堆内置类型的集合。 所以, 对于自定义的累, 取地址我们就会拿到第一个地址。 所以我们的起始地址 + 类型 = 起始地址 + 偏移量(这也是x86cpu的特点)

        所以, 如何理解资源分配? 线程资源分配的本质, 不就是分配地址的空间范围吗? 我们的线程分配资源, 本质就是把我们的地址空间划分一部分。 那么这个划分难不难呢? 怎么划分呢? 就比如我们有10个函数, 这个函数有没有地址? 代码有没有地址? 那么我们的某个线程使用了这个函数, 使用了某个代码, 那么他就天然的具有地址了。 

线程和进程的切换问题

        我们说线程比进程更加轻量化,为什么呢?

  •         a:创建和释放更加轻量化(生死问题)
  •         b: 切换更加轻量化(运行问题)

        总结就是整个生命周期线程都比进程更加轻量化。

        我们知道的是线程他自己肯定要有自己的上下文。 但是线程在切换的时候, 他对应的页表需要切换吗? 不需要。 地址空间需要切换吗? 不需要。 所以, 线程在切换的时候, 只是在局部切换, 它的页表和地址空间都不需要切换。 那么, 为什么说它的切换效率更高呢? 

        这个涉及到了cpu的知识。 就是当线程在运行的时候, 其实本质上就是进程在运行, 线程是进程的执行分支。在cpu当中, 除了有寄存器, cpu还会有一个硬件级别的cache。 cpu认为和物理内存交互太慢了, 所以就在自己里面集成了一块cache空间。 这块空间相对于寄存器很大, 相对于内存不大。 这部分cache被称为缓存的热数据。 

        那么, 线程切换, 在同一个进程内的线程切换 cache内的数据不需要或者说很少需要重新缓存。 但是进程切换, 那么cache内的数据要丢失并且重新缓存, 数据从冷变热, 需要花费时间。所以, 线程的切换要更加轻量, 而进程要更加重。

——————以上就是本节全部内容哦, 如果对友友们有帮助的话可以关注博主, 方便学习更多知识哦!!!  

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

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

相关文章

代码随想录算法训练营第四十六天 | 647. 回文子串,516.最长回文子序列

四十六天打卡,今天用动态规划解决回文问题,回文问题需要用二维dp解决 647.回文子串 题目链接 解题思路 没做出来,布尔类型的dp[i][j]:表示区间范围[i,j] (注意是左闭右闭)的子串是否是回文子串&#xff0…

2024.10月7~10日 进一步完善《电信资费管理系统》

一、新增的模块: 在原项目基础上,新增加了以下功能: 1、增加AspectJ 框架的AOP 异常记录和事务管理模块。 2、增加SpringMVC的拦截器,实现登录 控制页面访问权限。 3、增加 Logback日志框架,记录日志。 4、增加动态验…

Hunuan-DiT代码阅读

一 整体架构 该模型是以SD为基础的文生图模型,具体扩散模型原理参考https://zhouyifan.net/2023/07/07/20230330-diffusion-model/,代码地址https://github.com/Tencent/HunyuanDiT,这里介绍 Full-parameter Training 二 输入数据处理 这里…

netdata保姆级面板介绍

netdata保姆级面板介绍 基本介绍部署流程下载安装指令选择设置KSM为什么要启用 KSM?如何启用 KSM?验证 KSM 是否启用注意事项 检查端口启动状态 netdata和grafana的区别NetdataGrafananetdata各指标介绍总览system overview栏仪表盘1. CPU2. Load3. Disk…

3.使用条件语句编写存储过程(3/10)

引言 在现代数据库管理系统中,存储过程扮演着至关重要的角色。它们是一组为了执行特定任务而编写的SQL语句,这些语句被保存在数据库中,可以被重复调用。存储过程不仅可以提高数据库操作的效率,还可以增强数据的安全性和一致性。此…

RPA技术的定义与原理

RPA(Robotic Process Automation)即机器人流程自动化,是一种利用软件机器人或机器人工具来自动执行重复性、规则性和可预测性的业务流程的技术。以下是对RPA技术的详细介绍: 一、RPA技术的定义与原理 RPA技术通过模拟人工操作&a…

【redis-06】redis的stream流实现消息中间件

redis系列整体栏目 内容链接地址【一】redis基本数据类型和使用场景https://zhenghuisheng.blog.csdn.net/article/details/142406325【二】redis的持久化机制和原理https://zhenghuisheng.blog.csdn.net/article/details/142441756【三】redis缓存穿透、缓存击穿、缓存雪崩htt…

关于Linux查看系统及版本信息的命令lsb_release命令以及Centos7中将redis服务写入systemctl服务

一、关于Linux查看系统及版本信息的命令lsb_release命令 linux查看系统是centos还是ubuntu,之前一直使用uname -a以及cat /etc/issue。但在某个服务器上发些这些都不行。有一个更好用的命令:lsb_release -a。如执行时提示-bash: lsb_release: 未找到命令…

Vscode+Pycharm+Vue.js+WEUI+django火锅(三)理解Vue

新创建的Vue项目里面很多文件,对于新手,老老实实做一下了解。 1.框架逻辑 框架的逻辑都是相通的,花点时间理一下就清晰了。 2.文件目录及文件 创建好的vue项目下,主要的文件和文件夹要先认识一下,并与框架逻辑对应起…

计算机毕业设计 校内跑腿业务系统的设计与实现 Java实战项目 附源码+文档+视频讲解

博主介绍:✌从事软件开发10年之余,专注于Java技术领域、Python人工智能及数据挖掘、小程序项目开发和Android项目开发等。CSDN、掘金、华为云、InfoQ、阿里云等平台优质作者✌ 🍅文末获取源码联系🍅 👇🏻 精…

dayu_widgets-简介

前言: 越来越多的人开始使用python来做GUI程序,市面上却很少有好的UI控件。即使有也是走的商业收费协议,不敢使用,一个不小心就收到法律传票。 一、原始开源项目: 偶然在GitHub上发现了这个博主的开源项目。https://github.com/phenom-films…

YOLO11改进|SPPF篇|引入YOLOv9提出的SPPELAN模块

目录 一、【SPPELAN】模块1.1【SPPELAN】模块介绍1.2【SPPELAN】核心代码 二、添加【SPPELAN】模块2.1STEP12.2STEP22.3STEP32.4STEP4 三、yaml文件与运行3.1yaml文件3.2运行成功截图 一、【SPPELAN】模块 1.1【SPPELAN】模块介绍 下图是【SPPELAN】的结构图,让我们…

OOOPS:零样本实现360度开放全景分割,已开源 | ECCV‘24

全景图像捕捉360的视场(FoV),包含了对场景理解至关重要的全向空间信息。然而,获取足够的训练用密集标注全景图不仅成本高昂,而且在封闭词汇设置下训练模型时也受到应用限制。为了解决这个问题,论文定义了一…

环球资源网 海外 globalsource reese84 分析

声明: 本文章中所有内容仅供学习交流使用,不用于其他任何目的,抓包内容、敏感网址、数据接口等均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关! 有相关问题请第一时间头像私信联系我删…

Graph知识图谱融入向量数据库,带来RAG效果飞升

01. 前言 随着大型语言模型(LLMs)在各种应用中的广泛使用,如何提升其回答的准确性和相关性成为一个关键问题。检索增强生成(RAG)技术通过整合外部知识库,为LLMs提供了额外的背景信息,有效地改…

使用激光跟踪仪提升码垛机器人精度

标题1.背景 码垛机器人是一种用于工业自动化的机器人,专门设计用来将物品按照一定的顺序和结构堆叠起来,通常用于仓库、物流中心和生产线上,它们可以自动执行重复的、高强度的搬运和堆垛任务。 图1 码垛机器人 传统调整码垛机器人的方法&a…

【重学 MySQL】四十六、创建表的方式

【重学 MySQL】四十六、创建表的方式 使用CREATE TABLE语句创建表使用CREATE TABLE LIKE语句创建表使用CREATE TABLE AS SELECT语句创建表使用CREATE TABLE SELECT语句创建表并从另一个表中选取数据(与CREATE TABLE AS SELECT类似)使用CREATE TEMPORARY …

maven指定模块快速打包idea插件Quick Maven Package

问题背景描述 在实际开发项目中,我们的maven项目结构可能不是单一maven项目结构,项目一般会用parent方式将各个项目进行规范; 随着组件的数量增加,就会引入一个问题:我们只想打包某一个修改后的组件A时就变得很不方便…

企业数据安全防泄密要怎么做?七个措施杜绝泄密风险!

随着信息技术的快速发展,企业的核心数据已成为最具价值的资产之一。然而,数据泄露事件频发,不仅会给企业造成严重的经济损失,还会影响企业的声誉。因此,如何防止企业数据泄密已成为每个企业管理者关注的重点。以下是七…

利用特征点采样一致性改进icp算法点云配准方法

1、index、vector 2、kdtree和kdtreeflann 3、if kdtree.radiusSearch(。。。) > 0)