Linux 多线程 ( 多线程概念 )

news2024/9/27 19:19:48

文章目录

  • Linux线程概念
    • 什么是线程?
    • 二级页表
    • 线程的优点
    • 线程的缺点
    • 线程异常

Linux线程概念

什么是线程?

  1. 在一个程序里的一个执行路线叫做线程 thread ),更准确的定义为:“线程是一个进程内部的控制序列"。
  2. 一切进程至少有一个执行线程。
  3. 线程在进程内部运行,本质上是在进程地址空间中运行。
  4. 在linux系统中,CPU看到的PCB比传统的进程更加轻量化。
  5. 透过进程虚拟地址空间,可以看到进程的大部分资源,将进程资源合理分配给每个执行流,就形成了线程执行流。

我们在以前所学习的进程知识中,一个进程由进程控制块(task_struct),进程地址空间( mm_struct ) ,页表,页表与进程地址空间,物理内存的映射为关系构成。

在这里插入图片描述

每一个进程都有自己独有的进程地址空间和页表,对应的映射关系,所以我们在创建进程时需要耗费大量的时间,空间。

但是,对于Linux系统,如果我们在创建一个进程后只创建task_struct,并且这些task_struct共享进程地址空间,页表等相关资源,图示如下。
在这里插入图片描述
由于这些task_struct指向同一块进程地址空间和页表,所以它们所看到的资源都是一样的,我们可以让这四个task_struct执行不同的代码区域(栈区,堆区等以上区域),换句话说,我们之后创建出来的3个task_struct都可以同时执行自己的代码,从而完成”并发“。像这样,我们把这样的一份task_struct称这为”线程“。

  • 其中每一个线程都是当前进程里面的一个执行流,也就是我们常说的”线程是进程内部的一个执行分支“。
  • 线程在进程内部运行,本质就是线程在进程地址空间内运行,也就是说曾经这个进程申请的所有资源,几乎都是被所有线程共享的。
  • 线程比进程粒度更细,因为执行的代码和数据也更小了。
  • 线程调度的成本更低了,因为它在调度的时候,核心数据结构(进程地址空间和页表都不用切换了)

windows下的线程和Linux下的线程区别

Linux其实并没有真正对线程创建特定的数据结构

  • 线程本身是在进程内部运行的,操作系统中存在大量的进程,一个进程内又存在一个或多个线程,因此线程的数量一定比进程的数量多(线程 : 进程 一定是n : 1),当线程的数量足够多的时候,很明显线程的执行粒度要比进程更细。
  • 对于这么多的线程我们OS需要对其做管理(先描述,再组织),在大部分的OS中,线程都有一个tcb。如果我们的系统实现的是真线程,比如说windows平台,它就要分别对进程和线程设计各自的描述的数据块(结构体),并且很多线程在一个进程内部,所以还要维护线程tcb和进程pcb之间的关系。所以这样写出的代码,其tcb和pcb两个数据结构之间的耦合度非常复杂。但是对于Linux来说,在概念上并没有进程和线程的区分,只将task_struct叫做一个执行流,所以对于Linux来说,PCB和TCB为同一种。

Linux中使用pcb模拟tcb的优势:

  • 不需要单独设计tcb了。
  • 不用维护tcb和pcb之间的关系。
  • 不用打再编写调度算法了。

在Linux中,CPU是否能够识别当前调度的task_struct是进程还是线程?

不能,因为CPU只关心一个一个独立的task_struct(执行流),无论进程内部只有一个执行流还是有多个执行流,CPU都是以task_struct为单位进行调度的。只是这里的task_struct只执行一部分代码和数据,但也并不妨碍CPU执行其他执行流。

图示如下:
在这里插入图片描述

理解修改常量区

字符串常量区在代码区和已初始化数据区之间的,如果它不可被修改,那它是如何加载到物理内存呢?如何保证它不可被修改的?

比如当我们尝试修改字符串,字符串常量区经过页表的映射到物理内存,当它从虚拟地址到物理地址转换的时候,它是只读的,所以RWX权限为R,所以尝试在修改的时候直接在页表进行拦截,并结合MMU硬件转换,识别到只读但尝试修改的异常,发出信号,随后OS把此进程直接干掉。

如今,有了线程的引入,如何重新理解之前的进程?

我们红色方框框起来的内容,我们把这个整体叫做进程。
在这里插入图片描述

曾经我们理解的进程 = 内核数据结构 + 进程对应的代码和数据,现在的进程,站在内核角度上看就是:承担分配系统资源的基本实体。所有进程最大的意义是向系统申请资源的基本单位

因此,所谓的进程并不是通过task_struct衡量的,还需要创建地址空间、维护页表,然后在物理内存当中开辟空间、构建映射,打开进程默认打开的相关文件、注册信号对应的处理方案等等。

我们之前接触的进程内部只有一个task_struct,说明该进程内部只有一个执行流,也称之为”单执行流进程“。
在这里插入图片描述
如果进程内部有多个执行流,我们称之为"多执行流进程"。
在这里插入图片描述

  • Linux中,CPU实际上看到的task_struct实际上要比传统的task_struct更加轻量化,当进程只有一个执行流,那就说明等于OS内的进程,但是如果进程有多个执行流,那就说明该线程<其他OS内传统的PCB,CPU拿到的是进程中多执行流中某一个PCB,这某一个PCB并没有单独创建特定的数据结构,从宏观上,所以Linux下的进程统一称之为: 轻量级进程。
  • 线程(一个执行流)是CPU调度的基本单位。

原生线程库pthread

在Linux中,站在内核角度没有真正意义上线程相关的接口,但是站在用户角度,如果想创建线程,而不是只能用fork函数,所以提供了pthread原生线程库。

二级页表

二级页表引入

以32位平台为例,在32个比特位中一共可以存在2^32个地址,如果由虚拟地址空间通过页表映射到物理内存中,那么一个页表就需要2 ^32个表项存储地址。
在这里插入图片描述
并且,每一个表项除了要保存的虚拟地址以及物理地址这里就大概需要8字节,并且还需要保存一些权限信息,那么一个表项大概需要10个字节,那么最后意味着存储一张页表OS需要2^32 * 10个字节,那么也就是40GB。
在这里插入图片描述
可是,这远远超过了在32位平台下4GB的内存。

二级页表

实际上,OS在64位平台上是多级页表,在32位平台上是二级页表。

这里以32位平台为例,虚拟地址通过二级页表映射关系如下:

  1. 选择虚拟地址的前10个比特位在页目录中查找,通过映射关系找到对应的页表。
  2. 再选择虚拟地址的10个比特位在对应的页表中查找,通过映射关系找到对应的页框的起始地址。
  3. 最后将虚拟地址的剩下12个比特位作为偏移量从对应页框的起始位置后进行偏移,页框起始地址 + 偏移量 = 物理内存中对应的字节数据。

在这里插入图片描述
说明一下:

  • 物理内存是按照4KB单位进行划分的(每一个4KB单位叫做页框),可执行程序上也是被划分为4KB为一个单位为页帧,当可执行程序和加载到内存中时也是以4KB进行加载和保存的。
  • 如果一级页表有一张,那就说明有2^10个表项,进而表示有2 ^ 10个二级页表,并且1个表项 大概10个字节,那么总字节数便为2 ^ 10 * 10个字节,也就是10MB,这极大节省了空间。
  • 上面所述的映射过程,总共由页表和MMU硬件完成,其中页表为软件映射,MMU是一种硬件映射,OS由虚拟地址转换为物理地址采用的是软硬链接结合的方式。

上述页表的优势:

  • 进程虚拟地址管理和内存管理,通过页表 + page进行了解耦。
  • 页表分离了,可以实现页表的按需获取,没有用到的就不创建,进而节省了空间。

线程的优点

  • 创建一个新线程的代价要比创建一个新进程小得多。
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多,这里有两个核心原因:
  1. 线程切换是不需要切换页表和进程地址空间的,而进程与进程调度之间需要切换页表,进程地址空间
  2. CPU内部具有硬件L1~L3 cache缓存,在进程进行访问的时候,CPU提前就将目标代码和数据加载到CPU的缓冲区中,一个进程内部的执行流访问时就可以可以直接从缓冲区中读取,提高调度效率。但是如果进程间切换,因为进程具有独立性,cache立即失效,新进程执行的时候,只能重新对该进程预计执行的代码数据缓存。
  • 线程占用的资源要比进程少很多。
  • 能充分利用多处理器的可并行数量。
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务。
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现。
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

那么,一个进程中线程是不是越多越好?

不是,即便线程切换搜耗费的成本较低。但是如果线程过多,那么会造成线程之间的调度成本过大。

线程的缺点

  1. 性能损失
  • 一个很少被外部事件阻塞的计算密集型线程往往无法与共它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多,那么可能会有较大的性能损失,这里的性能损失指的是增加了额外的同步和调度开销,而可用的资源不变。
  1. 健壮性降低
  • 编写多线程需要更全面更深入的考虑,在一个多线程程序里,因时间分配上的细微偏差或者因共享了。
  • 不该共享的变量而造成不良影响的可能性是很大的,换句话说线程之间是缺乏保护的。
  1. 缺乏访问控制
  • 进程是访问控制的基本粒度,在一个线程中调用某些OS函数会对整个进程造成影响(一个线程可能会影响到其他线程运行)。
  1. 编程难度提高
    编写与调试一个多线程程序比单线程程序困难得多。

线程异常

  • 单个线程如果出现除零,野指针问题导致线程崩溃,进程也会随着崩溃,因为在Linux中线程就是进程的一部分。
  • 线程是进程的执行分支,线程出异常,就类似进程出异常,进而触发信号机制,终止进程,进程终止,该进程内的所有线程也就随即退出。

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

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

相关文章

ArcGIS Maps SDK for JavaScript系列之四:添加自定义底图

目录 Basemap类介绍Basemap类的常用属性Basemap类的常用方法 使用Basemap添加自定义底图引用Basemap引用切片图层创建一个新的Basemap对象将自定义图层应用到地图视图中引入并创建Camera对象引入并创建SceneView对象 Basemap类介绍 Basemap类是ArcGIS Maps SDK for JavaScript…

Linux:centos9的本地yum仓库配置

其实9和7的配置方法是差不多一样的&#xff0c;只不过你使用7的本地yum仓库里面直接挂载就可以直接把仓库位置指向挂载点 具体可以看我往期文章&#xff0c;但是先看完我下面的描述再去看我链接的文章才能看懂如何配置centos9的yum仓库 Linux&#xff1a;YUM仓库服务_鲍海超-…

Scratch游戏------打砖块(不用VIP)

打砖块游戏是一款较老的动作电子游戏。玩家操作“挡板”&#xff0c;让一颗不断弹来弹去的“球”击碎砖块&#xff0c;作为过关目标消去的“砖块”且的途中不会落到屏幕底下。 目录 1.操作说明&#xff1a; 2.背景&#xff1a; 3.挡板&#xff1a; 4.游戏控制&#xff1a; …

【算法训练-二叉树 二】【重建二叉树】依据前序与中序遍历序列重建二叉树

废话不多说&#xff0c;喊一句号子鼓励自己&#xff1a;程序员永不失业&#xff0c;程序员走向架构&#xff01;本篇Blog的主题是【重建二叉树】&#xff0c;使用【二叉树】这个基本的数据结构来实现&#xff0c;这个高频题的站点是&#xff1a;CodeTop&#xff0c;筛选条件为&…

基于matlab实现的电磁波反射折射动态仿真

完整程序: %题目要求电场入射到xo平面 clear; clc; u04*pi*1e-7; %自由空间中的磁导率 e01e-9/(36*pi); %自由空间中的电介质常数 f1e8; %电磁波的频率 w2*pi*f; Ei5*1.41; %入射波幅度 R0.052; …

Linux: Cache 简介

文章目录 1. 前言2. 背景3. Cache 硬件基础3.1 什么是 Cache &#xff1f;3.2 Cache 工作原理3.3 Cache 层级架构3.4 内存架构中各级访问速度概览3.5 Cache 分类3.6 Cache 的 查找 和 组织方式3.6.1 Cache 组织相关术语3.6.2 Cache 查找3.6.2.1 Cache 查找过程概述3.6.2.2 Cach…

函数式编程汇总

目录 一 . Lambda 表达式 实例 省略规则 二. Stream 流 案例数据准备 入门实例 调试技巧 常用操作 创建流 1. 单例集合 2. 数组 3. 双列集合 中间操作 1. filter 2. map 3. distinct 4. sorted 5. limit 7. flatMap 终结操作 1. forEach 2. count 3. max…

解决MySQL8.0本地计算机上的MySQL服务启动后停止没有报告任何错误

1.启动MySQL的错误信息如下 &#xff08;1&#xff09;“本地计算机上的MySQL服务启动后停止。某些服务在未由其他服务或程序使用时将自动停止。” &#xff08;2&#xff09;又在PowerShell中运行"net start MySQL"&#xff0c;服务启动失败。“MySQL 服务无法启…

27、git的安装和配置(自用简易版)

1.git的安装 安装没有什么好说的&#xff0c;运行安装包&#xff0c;一直下一步下一步&#xff0c;就好了 2.配置 首先配置用户名和邮箱吧 git config -global user.name "liu_liangyi"git config -global user.email 993261877qq.com配置好后可以查看一下,输入指令…

Java - LambdaQueryWrapper 的常用方法

1、查看项目中是否导入mybatisPlus的jar包 2、servie 层和实现类要集成mybatisPlus service 继承IService<> 实现类中要继承IService的实现类ServiceImpl<mapper,实体类> 3、如果想要mapper中的一些方法&#xff0c;mapper 要继承BaseMapper<实体类> 4、在实…

Nginx替代产品-Tengine健康检测

1、官网地址 官网地址&#xff1a;The Tengine Web Server 文档地址&#xff1a;文档 - The Tengine Web Server 健康检测模块&#xff1a;ngx_http_upstream_check_module - The Tengine Web Server 2、安装 下载 wget https://tengine.taobao.org/download/tengine-3.…

JAVA智慧物业源码 智慧物业系统源码

JAVA智慧物业源码 智慧物业系统源码 基于SpringBoot、Spring Security、Jwt、Vue的前后端分离的后台管理系统 编号&#xff1a;LQ8 1、系统环境 Java EE 8Servlet 3.0Apache Maven 3 2、主框架 Spring Boot 2.2.xSpring Framework 5.2.xSpring Security 5.2.x 3、持久层…

​全球人类读书会《乡村振兴战略下传统村落文化旅游设计》中国建筑出版传媒许少辉博士著作

​全球人类读书会《乡村振兴战略下传统村落文化旅游设计》中国建筑出版传媒许少辉博士著作

基于Xml方法的Bean的配置-实例化Bean的方法-构造方法

SpringBean的配置详解 Bean的实例化配置 Spring的实例化方法主要由以下两种 构造方法实例化&#xff1a;底层通过构造方法对bean进行实例化 构造方法实例化bean又分为无参方法实例化和有参方法实例化&#xff0c;在Spring中配置的<bean>几乎都是无参构造该方式&#xff…

如何把文件从本地上传云服务器

1、从服务器下载文件到本地&#xff08;如win电脑&#xff09; scp&#xff1a;命令&#xff0c; iss_train0110.33.16.2是服务器用户名&#xff0c;10.33.16.2是服务器ip&#xff0c; :是选择 /mnt/linaro/sample/sample/YOLOv8/cpp/yolov8_bmcv/yolov8_bmcv.soc&#xff1a;服…

AJAX 技术学习笔记(基础)

Asynchronous JavaScript And XML 概念&#xff1a;异步的 JavaScript 和 XML 原生 AJAX 介绍 作用&#xff1a; 和服务器进行数据交换&#xff0c;利用HTML一起代替耦合的JSP动态页面完成异步交互 同步交互和异步交互&#xff1a; 同步交互&#xff1a;客户端向服务器端发…

基于Java新枫之谷游戏攻略设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

JVM内存泄漏分析的demo

本文参考&#xff1a; JVM调优参数、方法、工具以及案例总结 JVM监控和调优常用命令工具总结 - Pickle - 博客园 (cnblogs.com) 面试官问我JVM调优&#xff0c;我忍不住了&#xff01; - Java3y - 博客园 (cnblogs.com) 从实际案例聊聊Java应用的GC优化 (qq.com) JVM调优的…

微分方程应用案例

下表1给出了近两个世纪美国人口统计表&#xff08;单位&#xff1a;百万&#xff09;&#xff0c;建立数学模型并检验&#xff0c;最后用它预报2010年美国的人口。 年 1790 1800 1810 1820 1830 1840 1850 1860 人口 3.9 5.3 7.2 9.6 12.9 17.1 23.2 31.4 年…

Chromedriver 在 Python 中查看源代码的方法

Python 中可以属性来查看需要爬取的网站的源代码。 对应具体的是&#xff1a;chrome.page_source 需要注意的是首先需要导入包 from selenium.webdriver import Chrome 然后进行初始化&#xff1a;chrome Chrome(serviceService(r"C:\Users\yhu\Downloads\chromedrive…