一文搞懂Linux多线程【上】

news2024/12/29 10:56:48

目录

🚩引言

🚩再次理解页表 

🚩初识线程

🚩线程和pthread库

🚀线程创建

🚩线程的资源共享问题

🚩线程的优缺点


🚩引言

今天,我们开始学习Linux中的线程部分。Linux线程和进程同等重要。接下来,我们将从什么是线程,线程的实现原理,为社么会有线程这个概念,线程和进程之间的关系等等方面来学习。我想告诉大家的是线程内容比较困难,希望大家克服困难。那么我们就开始吧!

🚩再次理解页表 

 在Linux中,我们对页表的概念日益丰富。从一开始的认为页表仅仅负责物理内存和虚拟内存地址之间的转化,然后又知道了不仅仅有用户级页表,也有内核级页表。但是,我们始终不知道页表是如何实现虚拟内存到物理内存之间的地址的转化的。今天,我们就把页表给研究透彻。

在磁盘中,代码被编译时,是按4KB空间大小为单位进行编译的,然后划分出了一个个单位大小的页帧。当代码和数据被加载到内存时,同样也是按照4KB为单位进行加载的。物理内存就划分出了若干个4KB大小的子空间,叫做页框。

这些页框也需要被操作系统给管理起来,管理方法为先描述,再组织。这块内容我就不详细阐述了,大家有兴趣的可以上网查一查。


我们以32位环境为例讲解。

在32位环境下,一个地址是32个比特位,这32个比特位从高权重开始被划分成了10,10,12的三组。

为什么要划分开呢?为什么要这么划分呢?我知道此时的大家心里一定有很多的疑惑,没关系,我们接着往下看。我想告诉大家:世界上的所有东西,都有它存在的理由。

我们先看第一组的10个比特位。这10个比特位表示的十进制数据范围为0----1023,共1014个数据。 这10个比特位对应的是页目录。

这个页目录有1024个空间,把上面的10个比特位的十进制数据当作偏移量,由高到低在页目录中查找。每个空间后面都对应着一张页表。

然后再看第二组的10个比特位,全排列的个数位1024个。我们可以把页表当作一个有1024个元素的数组,里边存放的是物理内存指定页框的起始地址。我们根据偏移量找到起始地址。然后就直接找到了物理内存。

然后到了最后的一组的12个比特位,12个比特位正好对应的是4KB页框的空间呀。我们根据这12个比特位的为地址,就在指定的页框中找到了我们要的数据。

总结一下,过程就是划分虚拟地址的比特位采用多级页表的方式进行查找的。

🚩初识线程

我们先回忆一下进程的概念

 我们知道:进程=内核数据结构+进程对应的代码和数据,一个进程的创建必然伴随着大量的数据结构来维护该进程,线程是不是也是这样呢?我们一会儿再谈。

此时的我们应如何看待虚拟内存呢?虚拟内存决定了进程看到的资源

 接下来,我们正式开始介绍我们的线程

如下图:

这就是一个进程的完整的结构。

此时,如果仅创建若干个task_struct结果体,让该结构体指向同一个虚拟内存空间,就形成了若干个执行流,每个执行流就是一个线程。所以,线程是进程内的一个执行流 。原来的一个进程的资源可以按照某种方式划分成若干份,每个线程获得其中的一小份资源。

 因为我们采用虚拟内存空间+页表的方式对资源进行划分。所以单个“进程”一定要比之前的进程执行力度更细。

为了方便大家理解,我举个小例子:

一个人被锁到了一件屋子里,这个人仅可以通过窗户看到外边的风景。有一天,又有几个人被关了进来。他们就平分这个窗户,每个人获得其中的一小部分,只可以通过这一小块窗户看到外边。人就是线程,窗户就是页表,外表的风景就是物理内存空间也就是资源。

操作系统作为软硬件资源的管理者,要不要对这些线程进行有效的管理呢?当然需要,管理的方式就是先描述,再组织。

 线程之间的关系如何表示,如何表示线程。操作系统如何选择线程进行执行。一切的一切都需要重新构建,其构建过程相当之复杂。所以有的操作系统对线程重新构建了一套数据结构。这样做的操作系统典型的是windows。

但是仔细观察我们不难知道:进程和线程的大多数属性是一样的,为了减少开发的成本和维护成本,我们为何不复用进程的相关数据结构呢?所以操作系统就基于进程的PCB结构体创建了线程的TCB(thread control block)。这样做的操作系统典型的就是Linux。所以对一个进程内的线程的管理就变成了对TCB的管理。 

这个结构体,我们先见一见就可以了,里边的东西我们会陆续知道的。

一句话:线程在进程内部运行,线程在进程的地址空间中运行,拥有该进程的一部分资源。 


学到现在,懵了。我们有必要再重新认识一下进程

什么是进程? 

现在的进程应该包括:若干个PCB和一个虚拟内存空间,若干个页表和物理内存中相关的代码和数据。创建这些结构对象极度依赖系统资源。

所以进程:在内核角度,是承担分配系统资源的基本实体 

我们刚刚学了线程,什么是线程呢?

线程就是CPU调度的基本单位,一个线程就是一个执行流。 

我们今天讲的进程概念和之前我们学习的进程冲突吗? 

毫不冲突。谁规定了一个进程内部必须有多个线程了?一个进程内部有一个执行流(一个执行流就是一个线程)依旧可以。所以我们可以把之前讲的进程认为是单线程的进程。  今天我们认为一个进程包括多个线程,一个进程只有一个线程当然也是可以的。所以之前的进程概念是今天我们学习的进程概念的子集,一个特例。


CPU在调度时,不关系调度的是进程中的哪一个执行流。它所关心的就是让我顺利调度就ok了。

 别人给它哪个,它就执行哪个执行流。至于给哪个线程来执行,这是进程应该考虑的问题。

 接下来,我们对如上的知识再次总结一下:

  1. Linux内核中并没有真正意义上的线程,Linux是使用进程PCB来模拟线程的,是一种完全属于Linux自己的线程方案。
  2. 站在CPU的视角,每一个PCB,都是一个轻量级进程。
  3. Linux线程是CPU调度的基本单位,而进程是承担分配系统资源的基本单位。
  4. 进程是用来整体申请资源的,线程是用来伸手向进程要资源的。
  5. Linux中没有真正意义上的线程。但是操作系统只认线程,用户和程序员只认线程。Linux无法提供创建线程的接口,只能提供创建轻量级进程的接口。所以诞生了线程库的概念
  6. Linux的这种设计方案好处是什么?一个系统越复杂,也就意味着出问题的概率越高,维护成本也就越高。Linux的设计方案简单,维护成本大大降低,可靠性高。便于长期对外提供服务。

 举个小例子:

在我们国家,承担分配社会资源的基本单位就是家庭。一般每个家庭的组成为:子女,父母,爷爷奶奶。子女学习,父母工作。爷爷奶奶可能退休了,他们要管好自己的身体,但是所有人都有一个共同目标,那就是把家里的生活变得越来越好。

国家就像操作系统,家庭像是进程,而每个家庭成员就是线程。但有的人就比较惨了,既无父母,也无子女,这样的家庭既是进程也是线程。

🚩线程和pthread库

pthread是任何Linux的操作系统都必须要有的。

我们提到,在Linux内核中并没有线程这样的概念,自然不会有创建线程的相关系统调用。但是程序员只认线程,所以程序员就自己编写了一个用户级线程库:pthread库

🚀线程创建

在Linux系统中,通过pthread库提供的pthread_create函数可以创建新的线程。该函数的原型如下:

       #include <pthread.h>

       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
  • thread : 输出型参数,用于获取创建成功线程的ID,该参数是一个输出型参数
  • attr(attribute 属性) : 用于设置创建线程的属性,传入NULL设置默认属性
  • start_routine(routine 常规) : 该参数是一个函数指针,即线程启动后需要执行的函数
  • arg (argument 参数) : 传给线程的参数

 返回值:成功返回0,失败返回-1,错误原因被设置。

当一个程序启动时,一个进程被操作系统进行创建,与此同时一个线程也立刻运行,这个第一个被创建的线程就是主线程。

即主线程就是产生其它子线程的线程,通常主线程必须最后完成某些执行操作,比如各种关闭动作

 接下来,我们做一个小实验

#include<pthread.h>
#include<iostream>
#include<unistd.h>
#include<cassert>
using namespace std;

void* pthread_routine(void *args)
{
    while(1)
    {
         cout<<"我是新进程,我正在运行"<<endl;
         sleep(1);
    }
   

}
int main()
{
    pthread_t tid;
    int n=pthread_create(&tid,nullptr,pthread_routine,(void *)"thread one");
    assert(n==0);
    (void)n;

    while(1)
    {
        cout<<"我是主进程,我正在运行"<<endl;
        sleep(1);
    }
    return 0;

}

 

注意。由于pthread是第三方库,所以我们在编译时,必须指明要链接的库名称。

在Linux中,查看轻量级进程的命令:ps  -aL 

在查询轻量级进程的查询项中有一个LWPlight weight pthread) 表示轻量级进程的ID。

 其中进程的PID和轻量级进程ID相等的线程为主线程,另一个为新线程。它们为两个不同的执行流。

🚩线程的资源共享问题

我们上面谈了,一个线程被创建,被分配得到相应的代码,然后运行。那么数据呢?一个进程内的若干线程的数据是如何保存的呢?我们做个实验

#include<pthread.h>
#include<iostream>
#include<unistd.h>
#include<cassert>
using namespace std;

int g_val=0;
void* pthread_routine(void *args)
{
    while(1)
    {
         cout<<"我是新进程,我正在运行,g_val:"<< g_val++<<"  &g_val: "<<&g_val<<endl;
         sleep(1);
    }
   

}
int main()
{
    pthread_t tid;
    int n=pthread_create(&tid,nullptr,pthread_routine,(void *)"thread one");
    assert(n==0);
    (void)n;

    while(1)
    {
        cout<<"我是主进程,我正在运行,g_val:"<<g_val<<"   &g_val:"<<&g_val<<endl;
        sleep(1);
    }
    return 0;

}

我们定义一个全局变量,然后由新线程对该全局变量进行++操作。从主线程端读取数据。 

从中,我们发现新线程将数据一改,主线程立刻就可以读取改过的数据。且主线程和新线程读取的地址是同一个。说明这个变量是被所有进程所共享的。

在进程中,线程一旦被创建,几乎所有的资源都是被所有线程所共享的。但一定也要有线程私有的成分。资源共享对线程来说是优势,同时也是劣势。我们将来要花费很多的时间来解决资源共享带来的一系列问题。

在家庭中,一般我们家庭的资源都是被所有家庭成员所共享的。例如:电视机,交通工具等等。 

 但是,也肯定存在成员见私有的东西,比如日记本,老年人吃的补品等等。

 在线程中,什么资源都是线程所私有的呢?

  • PCB属性私有,例如状态优先级。
  • 要有一定私有的上下文数据
  • 每一个线程都要有独立的栈结构

对于第三点,我有问题,虚拟地址空间中只有一个栈结构,怎样实现独立的呢?我们后边说。 

有观点认为,相比于进程切换,线程切换需要操作系统做的工作要小的多。为什么这样说呢?

  • 进程切换需要切换:上下文数据&&PCB&&虚拟内存&&切换页表
  • 线程切换需要切换:上下文数据&&PCB
  • 其主要的差异体现在cache上。线程切换时cache不用太更新,数据依旧可以使用;但是进程在切换时,数据需要全部更新。

 

 

其实,CPU是CPU内部的一个硬件级缓存,这个缓存速度比内存要快,但是比CPU运算的速度要慢。从内存中读取的数据要先经过cache,然后寄存器再从cache中读取数据。

一个运行正常的进程,cache中一定存在着大量的热点数据,线程在切换时,同属于一个进程,我们知道一个进程的大部分数据是被所有线程所共享的。所以线程在切换极有可能会继续使用cache中的热点数据。进程在切换时,这些热点数据一点用就没有了,需要全部加载。这些热点数据的形成是需要时间的,这段时间内cpu只能写透式的访问内存,所以操作系统要做更多的工作。

🚩线程的优缺点

 优点

  • 创建一个新线程的代价要比创建一个新进程小得多
  • 与进程之间的切换相比,线程之间的切换需要操作系统做的工作要少很多
  • 线程占用的资源要比进程少很多
  • 能充分利用多处理器的可并行数量
  • 在等待慢速I/O操作结束的同时,程序可执行其他的计算任务
  • 计算密集型应用,为了能在多处理器系统上运行,将计算分解到多个线程中实现
  • I/O密集型应用,为了提高性能,将I/O操作重叠。线程可以同时等待不同的I/O操作。

缺点

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

到这,本博客内容就到这里了,我们下期再见! 

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

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

相关文章

双 μC 的 PWM 频率和分辨率

该方法是过滤 PWM 信号的 HF 分量&#xff0c;只留下与占空比成正比的 LF 或 DC 分量。然而&#xff0c;低通滤波器并不能完全滤除PWM频率&#xff0c;因此LF/DC信号一般会有一些纹波。 有两种方法可以降低 PWM DAC 的纹波。可以降低低通滤波器的截止频率&#xff0c;或者提高…

javaScript(八) 对象

}, “arm”:{ //属性名 configurable:true, //允许删除&#xff0c;允许使用defineProperty()修改属性描述符 enumerable:true, //允许被搜索到 set:function (value) { //给这个属性设置一个函数&#xff0c;注意&#xff0c;调用对象的函数的方法是xm.xarm()&#xff0c;…

MacOS - ToDesk 无法远程操控鼠标键盘解决方案

问题描述 远程后发现画面显示正常&#xff0c;但是键盘和鼠标的操作没有响应。 原因分析 可能是辅助功能没有勾选ToDesk_Session的权限。 解决方案 系统设置 - 隐私与安全性 - 辅助功能 进去后找到 ToDesk&#xff0c;开关打开&#xff0c;输入系统密码&#xff0c;重启 App …

从0构建一个录制UI测试工具

很多UI自动化测试工具都具备录制UI自动化测试的能力&#xff0c;例如playwright&#xff0c;可以通过playwright vscode插件完成录制&#xff0c;如下图所示&#xff0c;当选择录制脚本时&#xff0c;会打开一个浏览器&#xff0c;在浏览器中输入被测应用url&#xff0c;用户在…

vscode用vue框架1写一个登陆页面

目录 一、创建登录页面 二、构建好登陆页面的路由 三、编写登录页代码 1.添加基础结构 2.给登录页添加背景 3.解决填充不满问题 4.我们把背景的红颜色替换成背景图&#xff1a; 5.在页面中央添加一个卡片来显示登录页面 6.设置中间卡片页面的左侧 7.设置右侧的样式及…

CPU1511作为CPU1513的智能IO设备

一、把一个IO控制器作为另一个IO控制器的IO设备来使用 1、在智能设备通信里定义好传输区后&#xff0c;导出GSD文件 2、在另一个项目程序内添加GSD文件 3、当作PLC的IO设备组态&#xff0c;并连接至PLC_1 4、在右侧更改I区、Q区地址与名称 5、硬件编译并下载&#xff0c;此…

天翼云服务器80、443等特殊端口无法访问原因记录

之前阿里云、腾讯云的服务器上&#xff0c;想要用域名访问项目简单配置就好了&#xff0c;这次甲方直接买的翼云的服务器&#xff0c;配置了半天&#xff0c;防火墙端口80、443端口开放了&#xff0c;控制台安全组也添加了&#xff0c;就是不能用域名或IP直接访问&#xff0c;配…

深入解析与解决高并发下的线程池死锁问题

问题背景 在现代互联网应用中&#xff0c;高并发场景是常态&#xff0c;为了高效处理大量用户请求&#xff0c;后端服务通常会采用线程池来管理线程资源。然而&#xff0c;在一个复杂的微服务架构项目中&#xff0c;我们遇到了一个棘手的问题&#xff1a;在业务高峰期&#xf…

解锁分布式云多集群统一监控的云上最佳实践

作者&#xff1a;在峰 引言 在当今数字化转型加速的时代&#xff0c;随着混合云、多云多集群环境等技术被众多企业广泛应用&#xff0c;分布式云架构已成为众多企业和组织推动业务创新、实现弹性扩展的首选&#xff0c;分布式云容器平台 ACK One&#xff08;Distributed Clou…

ONLYOFFICE 文档 8.1 现已发布:功能全面的 PDF 编辑器、幻灯片版式、优化电子表格的协作等等

&#x1f525; 个人主页&#xff1a;空白诗 文章目录 一、引言二、ONLYOFFICE简介1. 文档编辑器2. 电子表格编辑器3. 演示文稿编辑器4. 项目管理5. 邮件和日历6. 客户关系管理&#xff08;CRM&#xff09;7. 安全性和权限管理8. 多平台和第三方集成 三、安装1. Windows/Mac 安装…

斯坦福大学 AI 研究部门推出的“7 周人工智能学习计划”

①AI Python 基础&#xff0c;包括计算机原理、语法、判断语句等&#xff1b; ②AI Python 进阶&#xff0c;涉及 Linux 命令、多任务编程等&#xff1b; ③机器学习&#xff0c;涵盖算法、数据结构等&#xff1b; ④计算机视觉与图像处理&#xff0c;包含图像分类、目标检测…

每日鲜语携手中国国家高尔夫球队队员殷若宁征战巴黎,打响中国高端鲜奶品牌“鲜”声量

近日&#xff0c;高端鲜奶品牌每日鲜语宣布携手蒙牛品牌代言人、中国国家高尔夫球队队员殷若宁&#xff0c;共赴巴黎奥运赛场&#xff0c;为梦想挥杆而上。邀请众多消费者开启高品质、健康的生活方式。此举不仅彰显了每日鲜语作为高端鲜奶新标杆的品牌定位&#xff0c;也同时延…

COMSOL Multiphysics物理模拟软件 下载安装,COMSOL Multiphysics 卓越的模拟性能

COMSOL Multiphysics&#xff0c;它不仅具备卓越的模拟性能&#xff0c;还以其创新的多物理场耦合分析能力&#xff0c;为各类复杂问题提供了前所未有的解决方案。 这款软件的出现&#xff0c;犹如为科研工作者和工程师们插上了一双智慧的翅膀&#xff0c;使他们能够在物理世界…

RubyMine 2024 mac/win版:智慧编程,从心出发

JetBrains RubyMine 2024 是一款专为Ruby和Rails开发者打造的高效集成开发环境(IDE)。它凭借其卓越的性能和丰富的功能&#xff0c;帮助开发者在Ruby和Rails的开发过程中提升效率&#xff0c;减少错误。 RubyMine 2024 mac/win版获取 RubyMine 2024 提供了强大的代码编辑功能&…

VR加密方案常见问题有哪些?

在数字化时代&#xff0c;随着虚拟现实&#xff08;VR&#xff09;技术的迅速发展与普及&#xff0c;VR视频内容的安全传输成为关注焦点。为保护版权及敏感信息免遭非法复制或篡改&#xff0c;VR视频加密技术显得尤为重要。 首先&#xff0c;高效的加密算法对确保数据安全性至关…

60.Python-web框架-Django手动删除了一个数据库表,migrate问题

目录 1.问题产生 2.解决方法&#xff1a; 1.问题产生 今天手欠&#xff0c;删了一个数据库表&#xff0c;然后迁移不进来了。 当你在Django项目中手动删除了数据库模型&#xff08;models&#xff09;的表后&#xff0c;想要Django通过makemigrations命令重新创建或识别这些更…

第四届机械设计与智能制造国际会议(MDSM2024)

第四届机械设计与智能制造国际会议(MDSM2024) 4th International Conference on Mechanical Design and Smart Manufacturing 会议日期&#xff1a;2024年8月23-25日 会议地点&#xff1a;中国-武汉 会议官网&#xff1a;https://www.iaast.cn/meet/home/Bx118v7D 【支持单位】…

Java版商城:Spring Cloud+SpringBoot b2b2c实现多商家入驻、直播带货及免费小程序商城搭建

用技术&#xff1a; Spring CloudSpring BootMybatis微服务服务监控可视化运营 B2B2C平台&#xff1a; 平台管理端(包含自营) 商家平台端(多商户入驻) PC买家端、手机wap/公众号买家端 微服务&#xff08;30个通用微服务如&#xff1a;商品、订单、购物车、个人中心、支付…

C语言结构体包含结构体

C语言结构体可以包含另一个结构体&#xff1b; 下面通过一个例子看一下&#xff1b; struct Date {int day;int month;int year; };struct Person {char *name;struct Date birthday; }; ...... void CTestView::OnDraw(CDC* pDC) {CTestDoc* pDoc GetDocument();ASSERT_VAL…

.NET也能玩量化【2】搭建本地财经数据获取服务,以及获取复权历史大A数据演示...

直接动手&#xff0c;不写前言了。 创建.NET 8的一个webapi项目备用 本地安装python库 aktools 输安装完毕以后&#xff0c;输入 python -m aktools 即可启动有关服务 启动以后&#xff0c;主页地址&#xff1a;http://127.0.0.1:8080/ 查看swwager的api文档说明。我们主要使用…