【Linux】多线程相关第一篇:从进程谈起理解线程概念

文章目录

  • 为什么需要线程
  • 初步认识Linux线程
  • Linux操作系统的线程为什么要这么设计
  • 进程、线程关系梳理
  • 理解线程是CPU调度的基本单位
  • 简单认识多执行流如何划分代码

为什么需要线程

线程和进程的关系密不可分。

操作系统教材对于进程、线程的概念是这样描述的:

  • 进程是被加载到内存的程序,是承担分配系统资源的基本实体。
  • 线程是进程内部的执行分支,是CPU调度的基本单位。

很抽象,很不好理解,但是能够肯定的是,线程和进程有莫大关联,所以我们可以以进程为切入点来理解线程。

第一点,理解什么是进程

“进程是被加载到内存的程序”,这样的说法肯定是没错的,但是不够详细、不好理解,深入一点的话,我们可以这样来理解:进程 = 内核数据结构 + 程序代码跟数据。

解释如下:
首先,我们得有这样的一个认识,程序只是一个磁盘文件,它是我们写的代码经过编译器编译链接操作后生成的一个文件,它的内容包括二进制指令以及执行这些指令所需要的数据。在冯·诺依曼计算机体系结构中,CPU只与内存直接交互,磁盘属于外设,程序又存储在磁盘上,所以CPU无法获取到程序的指令和数据,程序要被运行起来就得先被CPU“看”到,然后CPU才能执行程序指令,这就要求操作系统先把程序拷贝到内存中,这个“拷贝”的过程就叫做“加载”。

其次,我们在计算机上可以启动很多的程序,这就意味着操作系统会把很多个程序文件拷贝到内存,内存上同时存在很多个被拷贝进来的程序文件,操作系统是计算机资源的管理者,它要将这些程序文件管理起来,管理分为两个步骤:先描述,再组织。

描述指的是操作系统内部会有一堆相关的结构体来记录内存级程序的各种信息,比如说进程PCB、进程地址空间、页表、文件描述符表等;组织指的是操作系统会将基于结构体创建的对象,通过顺序表、链表、队列等某些数据结构的形式组织起来,由于这是在操作系统内核层面上发生的,所以又称内核数据结构。

然后我们就能够得到这么一张结构图:
在这里插入图片描述

CPU要执行进程代码(指令),就可以通过进程控制块(PCB)访问进程地址空间正文代码区域中的虚拟地址,在经过页表转化之后就可以找到物理内存上的代码跟数据。

第二点,理解什么是多执行流

进程的源代码由程序员编写,其中包含了许多函数。这些函数在编译之后会被转换成指令块,每个指令块都有自己的入口地址,这个入口地址对应着原来的函数名。假设有一个进程A,它的源代码经过编译后包含了100个函数。当CPU调度执行进程A的代码时,它会从main函数开始执行,然后依次调用function1()、function2()、function3(),直到最后一个函数function99()被执行完毕。最终,main函数返回,整个进程的代码被CPU串行调度完成。

在A进程代码被CPU执行的过程中,我们发现任意时刻只有一个函数(或指令块)在被执行。像这种从执行开始到执行结束中不会存在两个函数同时被执行的过程称之为一个执行流。

理解到这里之后,我们当前可以简单认为一个进程就是一个执行流(后续被修正)。

假设现在又有一个程序,它需要处理大量数据。在单个进程中,程序需要依次处理每个数据块,这可能会导致处理时间较长。然而,如果我们将这个程序拆分成多个进程,每个进程负责处理数据的不同部分,那么这些进程就可以并行执行,每个进程独立地处理数据,将数据处理完之后,通过进程间通信,交还回给一个主进程,最终得出结果,从而加快整体处理速度。这种情况下,每个进程都代表了一个独立的执行流,它们同时在执行,实现了多执行流的效果。

我们希望多进程存在的目的不是为了实现多进程本身,而是为了多执行流并发执行进程代码从而提高运行效率。

第三点,多进程并发实现多执行流的时空成本极高

多进程并发实现多执行流这个方案不是完美的,它有一个缺点就是时间空间成本消耗极大。

以父子进程为例,由于进程具有独立性,这个独立性表现在,当父进程创建子进程时,操作系统会为子进程也创建出属于子进程自身的周边内核数据结构,还会用父进程的内核数据结构中的数据初始化子进程的内核数据结构,父子进程间代码共享,数据写时拷贝。

首先说时间成本,进程相关的内核数据结构是非常多的,这里只是选取的几个经典结构来举例,创建内核数据结构需要时间,初始化内核数据也需要时间,进程越多,准备工作所需要的时间就越大,达到一定程度就是,就会抵消掉多进程并发的优势。

然后是空间成本,内存的容量是有限的,进程越多意味着内核数据结构以及执行进程代码所需数据占据的内存空间就会越来越大。

在这里插入图片描述

所以,Linux就需要一种新的、代价更加小的方式来实现多执行流并发执行进程代码的技术,这个技术就是线程!

初步认识Linux线程

多进程并发实现多执行流的痛点在于进程的创建和初始化会带来额外的开销,在进程具有独立性的前提下,Linux必须确保每个进程都能够正常运行而不受其他进程的影响,因此,即使是相对简单的进程,也需要进行一系列的初始化操作,包括建立进程表、分配内存、加载可执行文件等。这些操作需要耗费时间和资源,并且可能会导致系统性能下降。

所以,Linux线程技术之一的目标是降低进程创建和初始化的开销。

关于进程地址空间,我们可以换一种角度来理解它,我们可以把进程地址空间及其内部的地址当成是一种资源。为什么这么说?

一个进程执行需要很多资源,如代码、数据、库、参数等,进程查找资源的时候都是通过进程地址空间来查找的,进程地址空间上的每一种资源都会有一个虚拟地址来作为唯一标识,可能是一行代码,可能是某个数据,可能是某个系统调用的入口,进程只要获得了一个合法的虚拟地址,经过页表的映射访问物理内存就能够找到对应的资源。

如果从资源的角度看待进程地址空间,进程地址空间是资源就意味着它在进程内部很多资源都是可共享、按需利用的,我们的目的不是要将一个执行流的进程代码拆分成多个执行流吗,之前是通过创建多个进行来完成这一操作,现在我们可以做这样的一件事,假设现在需要n个执行流,我们就创建n个进程PCB,这n个进程PCB都在一个进程内部,它们“看”到的都是同一个进程地址空间,然后通过某种方法,让这n个进程PCB执行地址空间中正文代码的某一部分,通过这样方式,原本进程内部只有一个执行流,现在进程内部就存在多个执行流,每个进程PCB就代表着一个执行流。

再回顾一下操作系统教材上的表述,“线程是进程内部的一个执行分支”,因此,进程内的一个进程PCB就象征着一个Linux“线程”。

在这里插入图片描述

Linux操作系统的线程为什么要这么设计

“线程和进程一样都是要被CPU调度的,线程是进程内部的一个执行分支,是CPU调度的基本单位”,这是操作系统学科告诉我们的概念,但是它没有告诉我们怎样做才能让线程在进程内部运行,怎样做才能让线程成为CPU调度的基本单位。

正式因为操作系统学科只谈方法论,不谈具体实现,换句话来说,只要实现的效果能够满足要求,不管操作系统内核的底层是如何实现的,只要能够遵守操作系统方法论中的这个概念,那它就是“线程”!

按照操作系统学科的描述,一般情况下,线程采取先描述,再组织的方式实现。

线程是进程内部的一个执行分支,一个进程内部可能会存在多个线程。一个操作系统运行起来会有很多进程在被调度,这就意味着,操作系统内部会存在比进程数量更多的线程。

那OS要不要创建线程呢?要不要按优先级调度线程呢?要不要对线程的上下文进行保存完成切换呢?一个线程执行完毕操作系统要不要对这个线程进行回收呢?说这么多的目的就一个,如果要在操作系统实现线程,就要对线程进行管理,就如同操作系统对进程做管理一样。

怎么管理,先描述,再组织。

和描述进程的PCB(Process Control Block,进程控制块)一样,操作系统内部也会存在一个叫做线程TCB(Thread Control Block,线程控制块)的内核数据结构来描述线程。如果说一个进程内部存在五个线程,那么操作系统就会分别为这五个线程创建TCP对象,然后通过某种数据结构统一管理起来,假如说这个数据结构是链表,那么,对线程的管理就从概念变成了对链表的增删查改,然后每个进程的PCB内部都会有一个指针指向属于自己的线程列表。线程TCP内也会有很多的用于描述线程属性的成员变量,比如说描述线程唯一标识符的,描述线程优先级的,描述线程上下文状态的……等等。

但实际上发现,如果真的这么去设计的话,整个操作系统就会变得非常复杂,且代码冗余,为什么这么说?

第一,线程和进程的管理操作非常相似,它们都需要操作系统进行调度、创建、终止等操作,因此线程和进程的控制块会有很多重合的属性;第二,也是更重要的,操作系统内部已经有了一套进程调度算法,如果线程被设计出来后,就意味着操作系统实现者就得去为线程再设计一套新的调度算法,操作系统在实际运作的时候就得先执行进程调度算法,再执行线程调度算法,这是不是有点太麻烦了?

在这里插入图片描述

综上,LInux的设计者认为,线程和进程都是执行流,二者具有高度的相似性,没有必要单独为线程设计数据结构与算法,能够直接复用进程代码,使用进程模拟线程。

这种通过复用进程代码来实现线程的方案带来了几个明显的好处:

  1. 简化系统设计和开发成本: 由于线程和进程具有高度相似性,因此通过复用进程代码来实现线程可以大大简化系统的设计和开发成本。不需要额外设计和实现线程管理、调度等功能,避免了重复造轮子的工作。

  2. 减少内核开销: 操作系统内核需要为每个进程和线程维护一些数据结构和元数据,如进程控制块(PCB)和线程控制块(TCB)。通过复用进程代码,可以减少内核开销,因为不需要为线程额外维护和管理独立的数据结构,而是直接利用了进程已有的数据结构。

  3. 统一管理: 通过将线程实现为进程的一部分,可以统一管理进程和线程,简化了操作系统的内部逻辑。这样一来,进程和线程之间的关系更加清晰,管理和调度也更加统一和一致。

  4. 更好的可移植性: 由于Linux系统已经实现了进程管理的功能,并且大多数操作系统都支持进程管理,因此基于进程的线程实现方案具有更好的可移植性。开发人员可以更容易地在不同的操作系统之间迁移和部署他们的应用程序。

进程、线程关系梳理

有了上面的了解之后,再来梳理一下进程和线程的概念和它们之间的关系。

线程是进程内部的执行分支,一个进程内部有多少个进程PCB就有多少个执行分支,即执行流(当然,这不代表着进程PCB就是线程,只是说一个进程PCB象征着一个线程)。

进程 = 内核数据结构 + 进程代码数据,在没有了解线程之前,我们一般会认为一个进程PCB代表着一个进程,但是现在要摒弃这个观点,因为没有谁规定一个进程内部就只能有一个进程PCB。不过现在,我们可以从内核角度来理解进程,即进程是承担分配系统资源的基本实体。举个例子来说,一个进程整体就像是一个小盒子,内存中占用了一块空间,盒子内的空间,分割成一个一个的小块,分别分给执行流、进程地址空间、页表、代码数据资源等。
在这里插入图片描述

理解线程是CPU调度的基本单位

从资源的角度来理解,我们能很好地对进程、线程做区分,可是如果是从调度的角度来理解,我们发现进程和线程之间的关系又变得模糊起来,进程能被调度、线程也能被调度、二者之间该怎么做区分?

在Linux中,无论是进程还是线程,它们都代表着一个执行流,都可以被CPU调度执行。过去,当进程只有一个执行流时,操作系统会将进程的控制块(PCB)链入CPU的等待队列,一旦轮到该进程,CPU就会调度该进程执行进程代码。但现在,由于一个进程内部可能有多个执行流,那么CPU是否需要区分这些执行流呢?

实际上,Linux内核并不区分进程和线程的调度。无论是进程还是线程的PCB,它们都能够访问进程地址空间中的代码和数据,都能够访问完成执行所需的所有资源。因此,站在CPU的角度,它们都是可以被完整执行的。在Linux中,不区分进程和线程,它们都是执行流的代表,这就足够了。

所以关于进程和线程,就存在这样一个关系:线程 ≤ 执行流 ≤ 进程

同时这里就再引出一个知识点,在Linux操作系统中,其实是没有 “线程” 这个概念的,因为Linux中没有一个真正意义上的独立的线程数据结构(TCB),所以 “线程” 这个说法只是为了与操作系统学科相结合,因此在Linux中只有两个东西,一个是进程,一个是进程内部的执行流,而对于执行流,它有一个正式的称呼,叫做 “轻量级进程”。

简单认识多执行流如何划分代码

一个进程的地址空间被所有进程PCB共享,所以按道理说每一个进程PCB都能看到完整的代码,那是怎么做到让一个进程PCB只看到一部分进程代码的,答案是页表!

下面就以32位平台为例,了解一下页表的结构。

众所周知,在 32 位平台下有 2 32 2^{32} 232 个地址,这就代表着有 2 32 2^{32} 232 个地址需要完成从虚拟地址到物理地址的映射。

一般来说,我们印象中的页表是长这样子的:
在这里插入图片描述

我们可以来算一下这样一张页表在内存要占用多少内存,页表中的一行有两个地址,一个地址占4字节,页表一行就占用8字节,32 位平台下,一个地址大小为4字节,有 2 32 2^{32} 232 个地址,总共占用的空间大小为 8 × 2 32 ≈ 2 35  字节 ≈ 16  GB 8 \times 2^{32} \approx 2^{35} \text{ 字节} \approx 16 \text{ GB} 8×232235 字节16 GB

32 位平台下总的内存容量才 4GB,实现一张页表却要 16GB,这多少有点离谱了,所以页表真正的结构肯定不是像上面这样子的,实际上一张完成的页表由一堆表构成,被称为 “二级页表”。

第一级页表(又称“页目录”):指针数组,包含指向第二级页表的指针。
第二级页表:指针数组,包含指向物理页框的指针。
页框:一个 4KB 大小的内存块。操作系统会将内存划分为 4KB 大小的内存块然后统一管理起来,简单来说就是可以理解为,内存就是一个超级大的数组,sizeof(这个数组中的一个元素) 得到的结果是 4KB,这个数组有 ( 4 G B / 4 K B = 1 , 048 , 576 ) (4GB / 4 KB = 1, 048, 576) (4GB/4KB=1,048,576) 个 元素。

对于一个从地址空间得到的虚拟地址,32位平台下虚拟地址转换成2进程有32个比特位,从虚拟地址到物理地址的映射过程如下:

  1. 访问虚拟地址的前10个比特位,在页目录找到对应的页表。
  2. 再选择虚拟地址的中间10个比特位,在页表中找到页框的物理起始地址。
  3. 将最后12个比特位作为偏移量从页框对应地址处向后偏移,找到物理内存中的某一个对应的字节数据, 2 12 2^{12} 212 刚好等于 4 KB。。

在这里插入图片描述

然后可以来算一下,这种结构下的页表实际占多少内存空间,一张页表就是一个指针数组,指针大小 4 字节,数组有 1024 个元素,一张页表总共消耗的内存为 4KB,一共有 1025 张页表(加上页目录),一个进程只需要不到 5M 的固定内存消耗,就能够完成虚拟地址到物理地址的映射。

回到一开始的问题,操作系统是怎么让不同的线程看到不同的进程代码的,这个其实在图中也体现出来了,可执行程序 hello.exe 被加载到内存中消耗 5 个页框,这 5 个页框就是进程的代码和数据,现在假设进程 hello.exe 内有三个线程,只需要让它们各自看到不同的二级页表,那它们就可以各自看到一部分的进程代码和数据,线程在被调度时就可以无干扰执行进程代码。

以上是线程概念相关的基本内容,有理解不到位的地方还请多多指出。
有理论就有实践,接下来要了解的就是线程空间相关的内容,即是编写代码实现线程的创建、终止、分离等操作,这里就放到下一篇文章中。

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

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

相关文章

秋招后端开发面试题 - JVM运行时数据区

目录 运行时数据区前言面试题JVM 内存区域 / 运行时数据区?说一下 JDK1.6、1.7、1.8 内存区域的变化?为什么使用元空间替代永久代作为方法区的实现?Java 堆的内存分区了解吗?运行时常量池?字符串常量池了解吗&#xff…

win server服务器 关闭危险端口 135,137,138,139,445的方法

通过防火墙来控制 打开控制面板 选择检查防火墙状态 选择高级设置 选择入站规则,再新建规则 选择端口,下一步 选择端口应用于啥协议,再指定端口,再下一步 选择阻止连接,下一步 下一步 给规则别名一下,方便…

STM32存储左右互搏 USB接口FATS文件读写U盘

STM32存储左右互搏 USB接口FATS文件读写U盘 STM32的USB接口可以例化为Host主机从而对U盘进行操作。SD卡/MicroSD/TF卡也可以通过读卡器转换成U盘使用。这里介绍STM32CUBEIDE开发平台HAL库实现U盘FATS文件访问的例程。 USB接口介绍 常见的USB接口电路部分相似而有不同的连接器…

Leetcode - 周赛396

目录 一,3136. 有效单词 二,3137. K 周期字符串需要的最少操作次数 三,3138. 同位字符串连接的最小长度 四,3139. 使数组中所有元素相等的最小开销 一,3136. 有效单词 本题就是一道阅读理解题: 字符串长…

Floyd+二分,蓝桥杯国赛2022[环境治理]

一、题目 1、题目描述 2、输入输出 2.1输入 2.2输出 3、原题链接 0环境治理 - 蓝桥云课 (lanqiao.cn) 二、解题报告 1、思路分析 考虑我们治理时间越长,灰尘度和越低,具有单调性 考虑 二分治理天数mid,1~n可以降低与其相连边 mid / n 点…

[Linux][网络][协议技术][DNS][ICMP][ping][traceroute][NAT]详细讲解

目录 1.DNS1.DNS背景2.域名简介 2.ICMP协议1.ICMP功能2.ICMP两类报文 3.ping命令4.traceroute5.NAT技术1.NAT技术背景2.NAT IP转换过程3.静态地址NAT && 动态地址NAT4.网络地址端口转换NAPT5.NAT技术的缺陷6.NAT和代理服务器 6.总结1.数据链路层2.网络层3.传输层4.应用…

APP未上架应用市场,微信商户如何快速开通APP支付

在移动互联网时代,APP作为企业服务用户的重要窗口,其支付功能的完善性直接关系到用户体验和企业的营收。然而,对于许多未上架应用市场的APP来说,如何快速开通微信APP支付功能成为了一个亟待解决的问题。本文将为您详细介绍在APP未…

AI大模型探索之路-训练篇22: ChatGLM3微调实战-从原理到应用的LoRA技术全解

系列篇章💥 AI大模型探索之路-训练篇1:大语言模型微调基础认知 AI大模型探索之路-训练篇2:大语言模型预训练基础认知 AI大模型探索之路-训练篇3:大语言模型全景解读 AI大模型探索之路-训练篇4:大语言模型训练数据集概…

Java | Leetcode Java题解之第79题单词搜索

题目&#xff1a; 题解&#xff1a; class Solution {public boolean exist(char[][] board, String word) {char[] words word.toCharArray();for(int i 0; i < board.length; i) {for(int j 0; j < board[0].length; j) {if (dfs(board, words, i, j, 0)) return t…

数据中心--AI时代的“炼油厂”

数据中心正在成为AI时代的“炼油厂”&#xff01; 众所周知&#xff0c;AI的高歌猛进催生了对数据的海量处理需求。为了满足蓬勃的算力需求&#xff0c;全球开启了新一轮的数据中心建设热潮&#xff0c;数据中心业务正在以指数级的速度疯狂扩张。 此番情景&#xff0c;和第二…

图书馆APP开发解决方案

uni-app框架&#xff1a;使用Vue.js开发跨平台应用的前端框架&#xff0c;编写一套代码&#xff0c;可编译到Android、小程序等平台。 框架支持:springboot/Ssm/thinkphp/django/flask/express均支持 前端开发:vue.js 可选语言&#xff1a;pythonjavanode.jsphp均支持 运行软件…

摸鱼大数据——Linux搭建大数据环境——安装无界面虚拟机

前期准备工作基础环境配置 提前安装好vmware软件,准备好连接虚拟机的&#xff08;Final shell、WindTerm、CRT皆可&#xff09; 安装CentOS纯净版&#xff08;无界面版&#xff09; 1.文件 -> 创建新的虚拟机 -> 典型(推荐) -> 稍后安装操作系统 2.客户机操作系统 : …

pytorch常用内置loss函数与正则化技术(补充小细节)

文章目录 前言一、常用损失函数(后面用到了新的会一一补充)1.1 回归中的损失函数1.1.1 nn.MSELoss()示例1:向量-向量示例2:矩阵--矩阵(维度必须一致才行)1.2 分类中的损失函数1.2.1 二分类(1)nn.BCELoss --- 二分类交叉熵损失函数示例1:向量-向量示例2:矩阵--矩阵(维…

FileZilla一款免费开源的FTP软件,中文正式版 v3.67.0

01 软件介绍 FileZilla 客户端是一个高效且可信的跨平台应用程序&#xff0c;支持 FTP、 FTPS 和 SFTP 协议&#xff0c;其设计宗旨在于为用户提供一个功能丰富且直观的图形界面。此客户端的核心特性包括一个站点管理器&#xff0c;该管理器能有效地存储和管理用户连接详情及登…

webpack5以下的项目,前端引入node的path模块需要额外配置

webpack5以下的项目&#xff0c;前端import * as path from path时需要额外配置&#xff0c;这里以vue.config.js为例 刚开始引入时报错 其实就是在打包前端项目的时候&#xff0c;将path模块替换成 path-browserify 模块&#xff0c;所以还需要安装 path-browserfify 模块 …

【全网玩家注意】Gamma 模拟器上架AppStore,让初代 PlayStation 游戏登陆 iPhone

5 月 11 日&#xff0c;索尼 PlayStation 的免费模拟器 Gamma 上架 iOS 和 iPad应用商店。Gamma 与开发商 Riley Testut 的Delta 模拟器相似&#xff0c;尤其是在界面方面。与 Delta 一样&#xff0c;它允许自定义屏幕控制器皮肤&#xff0c;并支持蓝牙控制器和有线键盘。 它支…

银发经济背后百万亿市场,解析冷门暴利的中老年AI赚钱项目!

最近“银发经济”这个词频繁出现&#xff0c;如果你注意到了抖音被封号的“秀才”以及仍在活跃的“一笑倾城”这两个账号&#xff0c;你就会明白中老年赛道的前景是多么广阔。 《银发经济蓝皮书》数据显示&#xff0c;到目前为止&#xff0c;我国60岁及以上的老年人口已超过2.8…

GNSS地表位移监测仪的工作原理

TH-WY1GNSS地表位移监测仪是一种用于实时监测地表位移变化的仪器设备。它主要利用全球导航卫星系统(GNSS)或全球定位系统(GPS)技术&#xff0c;通过接收卫星信号来测量地表点位的移动变化&#xff0c;从而获取地表点位的精确坐标信息&#xff0c;进而监测地表的水平和垂直位移情…

【软考】UML中的图之活动图

目录 1. 说明2. 图示3. 特性4. 使用方式4.1 对工作流建模4.2 对操作建模 1. 说明 1.Activity Diagram。2.活动图是一种特殊的状态图&#xff0c;它展现了在系统内从一个活动到另一个活动的流程。3.活动图专注于系统的动态视图&#xff0c;它对于系统的功能建模特别重要&#x…

重新定义社交:Facebook的未来愿景与颠覆力量

在当今数字化社会中&#xff0c;Facebook作为全球最大的社交媒体平台&#xff0c;其未来愿景和颠覆力量备受关注。本文将深入探讨Facebook如何重新定义社交&#xff0c;以及其未来发展中的新趋势和影响。 1. 智能化社交体验 Facebook致力于利用人工智能技术提供更智能、个性化…