线程概念,控制

一、线程概念

线程概念进程内部的一个执行流,轻量化

观点:进程是系统分配资源的基本单位,线程是CPU调度的基本单位

在理解线程之前,我们在谈一下虚拟地址空间

我们都知道进程是通过页表将虚拟地址转化为物理地址的,对于PCB,file我们已经了解了,所以,我们主要谈页表

虚拟地址和物理地址之间映射时,是通过字节映射的吗如果进程大小是4GB,那么一共就会有 4 * 1024 * 1024 * 1024个字节,如果是按照字节映射,那么页表的大小(虚拟地址和物理地址各占4字节,其它不考虑)就是 8 * 4 * 1024 * 1024 * 1024个字节,也就是32GB,一个进程的页表就这么大,这是不可能的。所以,肯定不是通过字节映射的

我们都知道磁盘和物理内存之间是通过4KB进行IO的,在逻辑上我们认为物理内存也是以4KB划分的物理内存上4KB划分的空间,我们把它叫做页框或者页帧如何理解页框呢

在物理内存中会有许多这样的4KB空间,有些空间被使用,有些未被使用…,那么OS要不要对这些空间进行管理呢?答案是要的,先描述在组织

在OS有一个 struct page就是用来描述物理内存4KB空间的,一个4KB空间对应一个 struct page,那么对空间已经描述了,那么该怎么组织呢?只需要用一个 struct page pages[]数组来管理就可以了,对物理内存的管理就转变为了对数组的增删查改

既然如此,在OS内部还需要保存物理地址这样的概念吗

不需要了,这个数组中每个内存块的大小是固定的(4KB),那么物理块的起始地址 = 数组下标 * 4KB,申请一个物理内存块,本质只要申请到 struct page,知道 struct page的下标,那么物理内存块的所有地址就都知道了

那么OS如何得知所有物理内存块的地址呢OS只需要得到 page数组的起始地址即可

结论文件,进程和物理内存之间的关系就转化为了 file,task_struct 和 page之间的关系了

在32位系统下,OS采用的是二级页表,从虚拟地址转换到物理地址,默认是没有直接转化到字节的。虚拟地址一共32个 bit,从左往右依次划分10个 bit,10个 bit,12个 bit,根据CR3寄存器里存储的页表起始地址,使用前10个 bit用来索引一级页表,一级页表中存在1024个页表项(存储的是下一级页表的起始地址),中间的10个 bit用来索引二级页表,二级页表存储的是物理页的起始地址,这样就可以找到物理页框的起始地址了,最后12个 bit用来做页内偏移

查页表只需要帮我们找到要访问的是哪一个页框就可以了

真正的物理地址 = 页框起始地址 + 页内偏移

页表的大小 = 4 * 1024,就是4KB的大小,每一个页表都是4KB大小,那么二级页表一共(1024 + 1)* 4KB的大小,对比于32GB,那可真是小太多了

细节1CR3寄存器保存的是当前进程页表的基地址,物理地址

细节2虚拟地址高20位相同,一定是连续存放在一个页框的,因为索引的时候访问的都是同一个页表的同一个位置

细节3如果知道任意一个虚拟地址,如何得到所处的页框

addr & 1111 1111 1111 1111 1111 0000 0000 0000

那如何得到 page结构体呢page 存储在一个结构体数组里,只需要得到数组下标就可以了,数组下标 = 页框号 / 4KB

细节4进程首次加载磁盘块的时候,OS做什么

内存管理,申请内存就是申请 page,得到 page的数组下标,进而得到页框的物理地址,填充页表

细节5如果访问的是 int呢?一个结构体呢?一个类变量呢所有变量只有一个地址,开辟空间时最小字节的地址

页表转换的时候,只能拿到第一个字节的地址,所以语言中存在一个类型的概念。起始地址 + 偏移量的方式就可以访问了

细节6如何理解写时拷贝

OS内,申请和管理内存是以4KB为单位的,写时拷贝也是以4KB为单位的,申请一个新的页表,更改映射关系

细节7我们用 new,malloc申请,怎么申请的时候1,4,n字节随意申请的呢

new,malloc底层一定要调用系统调用(brk,mmap),只有OS才能访问硬件,调用系统调用是有成本的,所以C,C++自己在语言层,会有自己的内存管理机制,类似STL中的空间配置器

现在,再来理解什么是线程。

//thread线程标示符,类似于进程pid,输出型参数
//attr,线程的属性,通常设置为nullptr
//start_routine回调函数,函数指针类型
//arg作为回调函数的参数
//成功返回0,失败返回错误码
int pthread_create(pthread_t* thread, const pthread_attr_t* attr, 
void*(*start_routine)(void*), void* arg); //创建线程,执行指定的回调函数


在这里插入图片描述

在这里插入图片描述

大家有没有发现问题呢?一个单进程代码,竟然同时让两个死循环跑起来了

一个可执行程序,一个进程有一套页表,那么,进程页表的本质是什么?是进程看到资源的"窗口",通过虚拟地址与物理地址的映射,看到内存当中的代码和数据

一个进程,两个死循环同时跑起来了,是让不同的线程执行不同的函数,本质是让不同的线程,通过拥有不同区域的虚拟地址,拥有不同的资源,通过函数编译的方式,进行了进程内部的"资源划分"

Linux中多线程的实现:

一个进程中可以有一个执行流,那么可以有两个,多个执行流吗?当然是可以的那么这些执行流(线程)也是需要被OS管理,OS需要对这些线程分配新的页表,文件,调度算法等资源吗?答案是不需要的只需要给线程分配PCB就可以了,线程是进程内部的一个执行流,执行的是进程内部的一部分代码资源没有必要浪费这么多的资源为线程分配新的页表等

Linux中,一个线程在进程内部运行,是如何运行的呢线程在进程的虚拟地址空间中运行,线程和进程共享同一个虚拟地址,页表等资源(体现了线程是进程内部的一个执行流)

如何体现线程的轻量化呢让不同的线程访问虚拟地址空间中的一部分资源

那么,要如何才能做到,让不同的线程看到自己的代码资源呢?以代码区为例:

让不同的线程未来执行不同的入口函数即可(函数编译的方式,进行进程内部资源的划分)

在Linux中,线程的实现是用进程模拟的,复用了进程代码和结构

那么,今天我们要如何理解进程和线程呢?

以前我们说 进程 = PCB + 自己的代码和数据可是今天进程里有许多的PCB,这要如何理解呢

以前我们讲的进程是内部只有一个执行流的进程,也叫做单线程的进程,而今天,我们需要对进程重新定义。

进程 = OS分配的所有 task_struct + 自己的代码和数据 + 页表、文件等资源

所以,我们说进程是承担分配系统资源的基本实体

在CPU的角度,是不区分线程和进程的,它只拿着 task_struct 进行资源的调度,所以,执行流我们把它叫做轻量级进程

线程(task_struct)自然而然就是CPU调度的基本单位了

验证:

ps -aL //查看所有的轻量级进程

在这里插入图片描述
在这里插入图片描述

Linux中不存在线程概念,只存在轻量级进程的概念,所以,Linux系统给用户提供系统调用,只能提供轻量级进程的系统调用

在这里插入图片描述

所有创建进程或者线程的系统调用底层都对 clone 进行了封装

但是这个系统调用使用起来非常麻烦,所以创建线程时需要使用pthread库,这个库对clone这个函数做了封装

CPU在获取物理地址时其实并不是直接通过MMU查找页表得到物理地址的,而是通过TLB(快表,其实就是缓存),如果TLB有虚拟地址到物理地址的映射就给CPU,否则就去查找页表,在页表中找到之后,把物理地址给CPU,同时把这条虚拟地址和物理地址的映射给TLB,进行缓存

线程的优点

1.创建一个新线程比创建一个新进程的代价小得多(进程需要创建PCB,虚拟地址空间,文件资源,页表等,线程只需要创建PCB,共享进程的其它资源)

2.与进程之间的切换相比,线程之间的切换需要OS做的工作要少很多

. CPU内有CR3寄存器,保存的是页表的基地址,进程间切换需要更新CR3寄存器的内容,线程间切换不需要,因为,同一个进程里所有线程拥有的是同一个页表

. TLB就是缓存虚拟地址和物理地址的映射关系,线程间切换TLB不需要更新(线程共享进程的虚拟地址空间),进程切换TLB需要更新

. CPU内有一个 cache 硬件,这个硬件就是用来缓存代码和数据的,在CPU访问内存中的代码和数据时,并不是不断的进行虚拟地址到物理地址之间的映射访问的,而是通过 cache 硬件访问的,cache 硬件会预先加载一部分代码和数据,线程切换时,cache 是不需要更新的,进程切换需要重新加载新的代码和数据

在这里插入图片描述
在这里插入图片描述

可以看到,cache 大小还是挺大的(这与系统有关,也有MB的)

3.线程占用的资源比进程少很多(线程拥有进程的完整资源,但线程不需要重复再分配共享资源,如虚拟地址空间,页表等,线程只需要维护少量资源,比如局部变量,函数调用,寄存器状态)

线程的缺点

1.性能损失(过多的线程使用同一个处理器,增加了线程调度,而可用资源不变)

2.健壮性降低(多个线程共享了不该共享的变量,缺乏保护)

3.缺乏访问控制(调用某些OS函数对整个进程造成影响)

线程独有的数据

线程ID

寄存器(线程上下文数据)

进程间多线程共享

同一地址空间(代码段,数据段...)

文件描述符表

每种信号的处理方式

当前工作目录

用户id,组id

写一段程序验证一下。

在这里插入图片描述
在这里插入图片描述

可以看到,线程之间是共享全局变量,函数和堆空间的。当然了,这只是一部分,毕竟线程是共享进程的虚拟地址空间的

二、线程控制

在这里插入图片描述
在这里插入图片描述

主线程运行3秒后结束,新线程10秒后才终止,但是主线程一旦退出,所有的线程都退出了,表示进程终止了

这是因为,进程创建时OS需要分配PCB等资源,那么当进程退出时,所有的资源也应该都要进行回收,所以,所有的线程都退出了

一般情况下,主线程应该最后退出,线程也需要等待,类似进程的 wait。要对新线程进行等待,否则,也会造成类似僵尸进程的问题

//成功返回0,失败返回错误码
//thread表明等待哪一个线程
//retval获取新线程退出时的退出信息
//阻塞等待,main thread最后退出,自动解决新线程的内存泄漏问题(僵尸问题)
int pthread_join(pthread_t thread, void** retval);

在这里插入图片描述
在这里插入图片描述

可以看到,在多线程等待时,一旦只要有一个线程崩溃,所有的线程都崩溃了,而进程之间具有独立性,即便是父子进程,子进程崩溃也不会影响父进程。所以说多线程的缺点是健壮性低

线程终止

. return

. exit

在这里插入图片描述
在这里插入图片描述

exit是用来终止进程的,变相导致所有的线程退出

. pthread_exit

void pthread_exit(void* retval);//线程退出

在这里插入图片描述
在这里插入图片描述

可以看到,使用系统调用退出线程,只会让调用该函数的线程退出,不会影响到其它线程

. pthread_cancel

//成功返回0,失败返回非0的错误码
//thread取消目标线程的线程标识符
//线程退出,退出信息设置为-1(PTHREAD_CANCELED,是一个宏值)
int pthread_cancel(pthread_t thread); //取消线程

在这里插入图片描述
在这里插入图片描述

通常用于主线程取消其它线程

那么,线程自己可不可以取消自己呢?

先认识一个系统调用。

//返回的是调用线程的id
pthread_t pthread_self(void);

在这里插入图片描述
在这里插入图片描述

可以看到,主线程创建新线程的 tid 与新线程获取自己的线程标识符是一样的

在这里插入图片描述
在这里插入图片描述

可以看到,线程自己取消自己也是可以的。但是,这里为什么会将这条语句打印两次呢

这是因为,pthread_cancel函数发送取消请求,对应的线程收到取消请求之后会在合适的点终止自己,不是立即终止

最佳实践取消线程的方法在主线程中使用 pthread_cancel,本来就是主线程取消其它线程的

线程的传参和返回值问题

前面我们介绍了 pthread_join 函数它的第二个参数就是将线程退出时的退出信息带出来,现在,我们就要聊聊这个参数了,它是怎么通过这个参数把线程的退出信息带出来的,毕竟我们只是使用了两个系统调用而已。

还记得C语言中的 fopen函数吗,它的返回类型是 FILE*类型的文件指针。那么这个FILE是什么呢?它有在哪里

这个前面我们是说过的,FILE是一个结构体,它在C标准库里,fopen函数返回文件指针的时候,就必然创建了一个FILE对象那么这个对象在哪里呢它应该就在fopen函数内部申请的,然后通过 return 返回

那么,在多线程这里,线程的概念是谁提供的pthread库提供的

那么,将来我们可以在一个进程中创建很多线程,在多个进程中呢?就会有更多的线程,所以,线程需不需要被管理呢?那些线程在被调度,那些线程退出了?答案是需要的

那么,就应该对线程进行先描述在组织像进程一样有一个结构体 struct tcb,那么,这个结构体在哪里呢?不要忘了,前面说了,线程的概念是pthread库提供的,所以,这个结构体应该在 pthread库里面。这个结构体中就会有线程的各种属性

将来线程退出时,return 将数据写入到结构体中,主线程在等待时,将等待线程的标示符 tid传入进去,就可以找到指定的线程了(结构体),然后通过第二个参数将结构体中的退出信息拷贝出来,不就拿到指定线程的退出信息了吗

分离线程

默认情况下,新创建的线程是 joinable 的,线程退出后,需要对其进行 pthread_join操作,否则,会造成类似僵尸进程的问题(资源泄露)

如果不关心线程的返回值,我们可以告诉系统当线程退出时,自动释放线程资源。这个时候就不需要进行 pthread_join了。

//成功返回0,失败返回错误码
//thread分离线程的线程标示符
int pthread_detach(pthread_t thread);

分离线程可以自己分离自己,也可以是其它线程分离目标线程

在这里插入图片描述
在这里插入图片描述

线程分离之后再去等待线程,就会出错

在这里插入图片描述
在这里插入图片描述

这个时候可能有人要问了,主线程把新线程分离之后,如果是主线程先退出呢。那进程都终止了,新线程不是也会终止吗?这个问题不用担心,因为真正的软件都是死循环的,新线程执行完自己的代码就会退出,主线程是最后退出的

接下来再聊一下,pthread_cancel函数取消目标线程之后得到的退出信息

在这里插入图片描述
在这里插入图片描述

三、线程ID及虚拟地址空间布局

在这里插入图片描述

我们说过,线程是由 pthread 库提供的,所以线程是依赖于 pthread 库的,将来 pthread 库也要被加载到内存里。那么,一个进程中可以有许多线程,也可以加载许多进程啊这些进程中都会包含许多子线程,那么这些进程也是需要将 pthread 库映射到自己的虚拟地址空间中的,调用mmap 系统调用实现的

在这里插入图片描述

前面我们说过,线程有几部分资源是独占的,线程id、一组寄存器(线程的硬件上下文数据)、栈。描述线程的结构体是由 pthread库维护的,线程栈并不是在虚拟地址空间中的栈区上的,而是在共享区上,由 pthread 库在共享区上申请的一块固定的内存空间,主线程的栈是在虚拟地址空间上的

那么,什么是线程局部存储

前面我们说过,全局变量也是多线程之间共享的,那么如果我们要使线程之间独自私有呢?

在这里插入图片描述
在这里插入图片描述

像这样的就是线程局部存储

今天的内容分享就到这里了,觉得不错的给个一键三连吧。

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

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

相关文章

RabbitMQ 高可用实战篇(Mirrored Queue + Cluster + 持久化整合)

RabbitMQ 高可用实战篇(Mirrored Queue Cluster 持久化整合)1. 前言 在生产环境中,单节点 RabbitMQ 容易因故障导致消息丢失或业务中断。 通过高可用队列、集群部署和持久化策略,可以保证 消息可靠性、节点容错和持续服务。 本文…

支持向量机:从理论到实践

支持向量机:从理论到实践 文章目录支持向量机:从理论到实践一。理论概述1. 线性可分支持向量机1.1 基本概念与数学形式1.2 函数间隔与几何间隔1.3 间隔最大化与优化问题1.4 拉格朗日对偶理论与求解1.5 支持向量与决策函数2. 近似线性可分数据&#xff08…

LVS与Keepalived详解(二)LVS负载均衡实现实操

文章目录前言一、LVS-DR 模式详解1.1 数据包流向分析1.2 DR 模式的特点二、LVS-DR 集群部署实战2.1 环境准备2.2 配置负载调度器(Director Server)2.3 配置节点服务器(Real Server)2.4 测试验证三、前期回顾3.1 LVS 三种工作模式及…

归一化实现原理

归一化(Normalization)是一种将数据转换到相同尺度的预处理技术,它通常用于让不同特征(或数据项)具有相同的量纲或范围。在联邦学习中,归一化可以用来处理非独立同分布(Non-IID)**数…

企业级实战:构建基于Qt、C++与YOLOv8的模块化工业视觉检测系统

一、概述 在追求高效与精密的现代制造业中,自动化光学检测(AOI)已成为保障产品质量的核心技术。传统的质检流程往往受限于人工效率与主观判断,难以满足大规模、高精度的生产需求。本文旨在研发一套完整的、企业级的工业视觉异常检…

【目标检测】metrice_curve和loss_curve对比图可视化

代码如下: import warnings warnings.filterwarnings(ignore)import os import pandas as pd import numpy as np import matplotlib.pylab as pltpwd os.getcwd()names [model1, model2, model3,ours]plt.figure(figsize(10, 10))plt.subplot(2, 2, 1) for i in …

【LeetCode hot100|Week2】滑动窗口,子串

笔记用于个人复习和巩固,题解非原创,参考LeetCode官方题解以及各个大佬的解法,希望给大家带来帮助,同时笔记也能督促我学习进步 这周主要把滑动窗口和子串的题目刷了一遍 文章目录Week2D1 滑动窗口209. 长度最小的子数组713. 乘积…

vue2纯前端对接海康威视摄像头实现实时视频预览

vue2纯前端对接海康威视摄像头实现实时视频预览一、环境准备二、代码集成1.1 准备webrtcstreamer.js,粘贴即用,不用做任何修改1.2 封装视频组件,在需要视频的地方引入此封装的视频组件即可,也是粘贴即用,注意其中impor…

Android 设置禁止截图和禁止长截图

1.禁止截图 在 Activity 代码中 , 可以在调用 setContentView 函数之前 ,为 Window 窗口对象 设置 LayoutParams.FLAG_SECURE 标志位 , 可以禁止对本界面进行截屏 ,Window 窗口对象 , 可通过 getWindow 方法获取 ,核心代码如下 :getWindow().setFlags(LayoutParams.FLAG_SECUR…

AR 巡检在工业的应用|阿法龙XR云平台

AR 巡检的应用覆盖电力、石油化工、智能制造、轨道交通、冶金等对设备可靠性和安全性要求极高的行业,具体场景包括:电力行业变电站内设备的状态检查:通过 AR 眼镜扫描设备,实时显示设备额定参数、历史故障记录、实时传感器数据&am…

【C++】STL详解(七)—stack和queue的介绍及使用

✨ 坚持用 清晰易懂的图解 代码语言, 让每个知识点都 简单直观 ! 🚀 个人主页 :不呆头 CSDN 🌱 代码仓库 :不呆头 Gitee 📌 专栏系列 : 📖 《C语言》🧩 《…

深度学习周报(9.8~9.14)

目录 摘要 Abstract 1 LSTM相关网络总结与对比 1.1 理论总结 1.2 代码运行对比 2 量子计算入门 3 总结 摘要 本周首先总结了LSTM、Bi-LSTM与GRU的区别与优缺点,对比了三者实战的代码与效果,还另外拓展了一些循环神经网络变体(包括窥视…

Quat 四元数库使用教程:应用场景概述

基础概念 四元数是一个包含四个元素的数组 [x, y, z, w],其中 x,y,z表示虚部,w 表示实部。单位四元数常用于表示3D空间中的旋转。 1. 创建和初始化函数 create() - 创建单位四元数 应用场景:初始化一个新的四元数对象,通常作为其他…

【Java后端】Spring Boot 多模块项目实战:从零搭建父工程与子模块

如何用 Spring Boot 搭建一个父工程 (Parent Project),并在其中包含多个子模块 (Module),适合企业级项目或者需要分模块管理的场景。Spring Boot 多模块项目实战:从零搭建父工程与子模块在日常开发中,我们经常会遇到这样的需求&am…

企业级AI会议系统技术实现:快鹭如何用AI重构会议全流程

摘要 本文深度解析快鹭AI会议系统的核心技术架构,重点探讨其在语音识别、自然语言处理、数据集成和安全防护等方面的技术实现。通过对比传统会议系统的技术痛点,分析快鹭AI如何通过技术创新实现会议筹备时间减少67%、数据调取速度提升100倍的显著效果。…

【CSS学习笔记3】css特性

1css三大特性 1.1层叠性:就近原则,最新定义的样式 1.2继承性:子标签集成父标签的样式,如文本和字号 行高的继承:不加单位指的是当前文字大小的倍数 body {font: 12px/1.5 Microsoft YaHei;color: #be1313;} div {…

[C语言]常见排序算法①

1.排序的概念及常见的排序算法排序在咱们日常生活中十分的常见,就好比是网上购物的时候通常能够选择按照什么排序,比如价格、评论数量、销量等。那么接下来咱们就来了解一些关于排序的概念。排序:所谓排序,就是使一串记录&#xf…

文献阅读笔记:RS电子战测试与测量技术文档

信息来源:罗德与施瓦茨(Rohde & Schwarz)公司关于电子战(Electronic Warfare, EW)测试与测量解决方案专业技术文档。 该文档由台湾地区应用工程师Mike Wu撰写,核心围绕电子战基础、雷达系统、实战应用及…

别再纠结 Postman 和 Apifox 了!这款开源神器让 API 测试更简单

别再纠结 Postman 和 Apifox 了!这款开源神器让 API 测试更简单🔥 作为一名开发者,你是否还在为选择 API 测试工具而纠结?Postman 太重、Apifox 要联网、付费功能限制多?今天给大家推荐一款完全免费的开源替代方案 ——…

微调神器LLaMA-Factory官方保姆级教程来了,从环境搭建到模型训练评估全覆盖

1. 项目背景 开源大模型如LLaMA,Qwen,Baichuan等主要都是使用通用数据进行训练而来,其对于不同下游的使用场景和垂直领域的效果有待进一步提升,衍生出了微调训练相关的需求,包含预训练(pt)&…