【Linux系统】线程

目录

一.线程的概念

(1)地址空间是进程的资源窗口

(2)轻量级进程

二.线程的理解

1.Linux中线程的实现方案

2. 线程VS进程

3.线程比进程更加轻量化

4.线程的优点

 5.线程的缺点

6.线程共享的资源

7.线程私有的资源

三.地址空间虚拟到物理的转化

1.页框

2.重新理解文件缓冲区

3.页表

四.线程控制

1.pthread库

2.线程传参

3.线程终止

4.线程等待

 5.线程分离

6.线程取消

五.pthread库

1.pthread库中的线程

2.系统调用clone

3.pthread库管理线程

4.语言层面的线程库

5.线程的局部存储

总结


一.线程的概念

操作系统教材对于线程的定义如下:

  1. 线程是比进程更加轻量化的一种执行流
  2. 线程是在进程内部执行的一种执行流

(1)地址空间是进程的资源窗口

进程运行时,代码,数据,堆区,栈区,动态库,操作系统内的系统调用代码和数据,都是通过地址空间+页表的形式来访问的,所以地址空间是进程的资源窗口 

(2)轻量级进程

创建一个进程操作系统要做很多工作:创建进程PCB,地址空间,页表。将可执行程序从磁盘加载到物理内存,如果其中依赖了动态库还要加载动态库,在页表中构建虚拟地址到物理地址的映射关系,打开标准输入,标准输出,标准错误流,初始化文件描述符表,初始化信号相关pending,block,handler表,还要将进程的状态设置成R状态,链入CPU的运行队列。

创建一个进程的时间和空间成本挺高的。那么创建这样一种“进程”,只创建一个PCB,该PCB和之前存在的PCB指向同一个地址空间。利用技术手段,将代码分成若干份,数据该共享的共享,该分离的分离,本质就是将地址空间分配给若干个“进程”。很显然,这种“进程”比传统的进程要轻便很多。当CPU调度这种轻量级进程时,只会执行进程的一部分代码,访问进程的一部分数据,完成进程任务的一部分。这种轻量级进程就叫做线程。

  1. 线程只是参与资源分配,并不重新申请资源,所以线程创建更加简单,更加轻量化
  2. 线程在进程的地址空间中运行,所以线程在进程内运行

二.线程的理解

1.Linux中线程的实现方案

Linux没有为线程专门设计一种内核数据结构来描述它,因为线程和进程高度类似。所以Linux仍然以进程PCB来描述线程,并且复用进程管理的代码来管理线程。而Windows为进程和线程分别设计了内核数据结构,并且使用两种不同的管理方案。

一个进程内部可能有一个执行流,也可能有多个。Linux当中并不存在真正意义上的线程,它是用进程的PCB来模拟线程。所以严格来说,PCB是一个执行流的描述符,每个PCB代表一个执行流,将这种执行流称为轻量级进程。

当CPU拿到一个PCB时,不管他是一个进程当中唯一的执行流,还是进程众多执行流中的一个,不作区分,统一认为他是一个轻量级进程,有地址空间和页表。

2. 线程VS进程

进程是承担系统资源的基本实体,线程是CPU调度的基本单位。 

进程就像一个家庭,以家庭为单位分配社会资源。线程就像家庭成员,各自执行自己的任务,最终完成进程的任务。家庭由家庭成员组成,只不过有的家庭只有一个人;进程由线程组成,只不过有的进程只有一个线程。

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

void* ThreadRoutine(void* arg)
{
    const char* threadname = (const char*) arg;
    while (true)
    {
        cout << "I am a new thread: " << threadname << "pid: " << getpid() << endl;
        sleep(1);
    }
}
int main()
{
    //已经有进程了
    pthread_t tid;
    pthread_create(&tid, nullptr, ThreadRoutine, (void*)"thread 1");
    while (true)
    {
        cout << "I am main thread, pid: " << getpid() << endl;
        sleep(1);
    }
    return 0;
}

ps -aL : 查看轻量级进程(L即light) 

LWP(light weight process)即轻量级进程编号,即线程编号,主线程编号等于进程编号。CPU调度时看的是LWP,判断一个线程是不是主线程,只需看它的LWP是否和PID相等。

3.线程比进程更加轻量化

  1. 线程创建的成本更低(前文讲过)
  2. 线程切换的成本更低。一方面,硬件上下文切换的较少,即要切换的寄存器更少。但更主要的是,CPU内集成了高速缓存cache,根据局部性原理,CPU会把接下来有较高概率访问,或者高频访问的代码和数据,缓存到cache里,保存在cache里的数据叫做热数据。线程间切换不需要切换cache,因为线程(一个进程内的)的资源窗口是一样的,共享绝大部分数据。虽然可能会因为切换线程导致热数据失效,但这并不影响,重新缓存就好了。但是进程间切换,这个cache肯定失效了,必须重新缓存。

4.线程的优点

  1. 创建线程,调度线程,释放进程,操作系统做的工作都比进程少
  2. 多个线程可以并行被多个CPU调度
  3. 计算密集型(加密解密,打包压缩),将计算任务分解到多个线程,在多CPU上运行
  4. I/O密集型(下载,上传),将I/O任务分解到多个线程,线程可以同时等待不同的I/O操作,等待时间重叠,减少总的等待时间。

注:3,4是对2点的补充说明

 5.线程的缺点

  1. 线程间的独立性差,缺乏访问控制。由于线程共享同一个地址空间,理论上来说,地址空间中的所有数据都是共享的,即使是一个线程定义的局部变量(传递地址)。数据一旦共享,就会带来数据不同步问题。
  2. 健壮性降低。任何一个线程出问题,例如收到信号,整个进程都会崩溃。
  3. 编写代码和调试代码的难度提高。

6.线程共享的资源

  1.  文件描述符表,多个PCB指向同一个files_struct

  2. 信号处理方法,因为handler表是共享的,没有直接存储在PCB中

  3. 当前工作目录cwd,虽然cwd存储在PCB中,但是使用系统调用更改cwd,进程内多个PCB的cwd会同步更改

  4. 页表,地址空间及其中的代码和数据

7.线程私有的资源

  1.  线程id

  2. 寄存器里的数据(硬件上下文)

  3. 独立的栈结构

  4. errno

  5. 调度优先级

  6. pending表和block表

注:2,3点最重要

三.地址空间虚拟到物理的转化

1.页框

物理内存被划分为若干个4KB,这一个个4KB空间叫做页框(page),4KB叫做页大小(page size)。。文件系统IO的基本大小也是4KB,磁盘可看做由若干个4KB组成,这一个个4KB空间叫做文件块。

也即,操作系统将数据从磁盘上,以4KB为单位加载到内存,放到内存中的一个个页框中。

操作系统对内存的管理,转化为对所有页框的管理。先描述:内核中有描述页框的数据结构struct page,包含page的属性,以及状态(是否已经使用,是否正在向外设刷新,是否被修改过等)。再组织:结构体数组struct page pages[]将所有页框组织起来。

2.重新理解文件缓冲区

文件缓冲区并不是一块物理内存,而是一种数据结构,指向特定的页框的起始地址或者编号。文件缓冲区本质是一种数据结构指向另一种数据结构。

3.页表

页表的功能是记录虚拟地址到物理地址的映射关系,但页表并不是对每个字节(地址)都映射的,否则整个内存连一个页表都装不下。

以32位地址为例,32位被划分为三部分,10+10+12。进程页表也不止一张,页表被分为两级。

第一级叫做页目录,页目录最多有2^10=1024项。页目录就是一个数组。拿32位地址的前10位去页目录中去索引,这10位地址也就是数组的下标。所以映射关系中的虚拟地址那一项根本不用存,即采用直接定址法。

第二级叫做页表项,页目录中存储的是页表的物理地址。和页目录类似,32位地址的中间10位,就是页表项数组的下标。页表项中存储的是页框的起始地址。

所以通过页表可以找到物理内存中特定的页框,但我们要定位的是某个字节。实际上,物理地址=页框的起始地址+虚拟地址的后12位。也就是说,虚拟地址的最后的12位,是页内偏移量。

小结:页表内存储的是页框的起始地址,不是字节的地址。

用虚拟地址前10位去页表目录索引,找到特定页表项;再用虚拟地址的中间10位去页目录索引,找到页框的起始地址。目标物理地址=页框起始地址+页内偏移量

细节:
一级页表常驻,二级页表按需创建

CPU内有寄存器保存进程的页目录地址

将进程的资源分配给线程,本质就是划分页表 ,划分页表也就是划分地址空间!!!

想要访问一块内存,要么通过变量,要么通过地址。变量有作用域限制,但地址没有,并且变量本质是地址+地址偏移量(由变量类型决定)。所以在进程和线程的视角,虚拟地址本身就是资源,谁拥有的虚拟地址越多,范围越大,谁就能访问更多资源。

四.线程控制

1.pthread库

Linux操作系统内核没有线程,只有轻量级进程的概念,所以Linux只会提供轻量级进程创建的系统调用,不会直接提供线程创建的接口。在内核和用户之间,有一层中间的软件层,对用户提供线程控制接口,封装内核中轻量级进程的接口。这个软件层就是pthread原生线程库,<pthread.h>就是库的头文件。

内核中的轻量级进程模块是线程的实现层,线程库是接口层。将内核与线程库分离,这种做法将满足用户需求的接口和实现方案解耦,内核更新和线程库更新,二者互不影响。

pthread库下文会详谈。

2.线程传参

arg的类型是void*,意味着可以传递任意类型的指针。通常在主线程定义一个对象,传参时对其取地址

3.线程终止

  1. 在线程执行的函数中return
  2. exit()会终止整个进程
  3. pthread_exit()终止调用该函数的线程
  4. pthread_cancel()终止指定的线程(下文详谈)

4.线程等待

  1. 线程退出,没有等待会导致类似进程的僵尸问题(PCB一直不释放)
  2. 主线程如何等待新线程退出,获取退出信息?-> pthread_join

retval是一个输出型参数,由用户传入,为了接收线程的返回值void*,所以得用二级指针。通常在主线程定义一个void* 的retval,传入&retval。

进程等待vs线程等待:

  1. 进程退出信息有退出码和退出信号,但是线程退出只有退出码(void*),没有退出信号。因为线程一旦收到退出信号,整个进程都会终止,主线程连获取退出信息的机会都没有。
  2. 进程可以选择阻塞式等待和WNOHANG,但是线程只能阻塞式等待

 5.线程分离

一个线程被创建时,默认是可连接状态(joinable),这意味着当线程终止后,其资源不会被立即回收,直到其他线程调用pthread_join()来获取该线程的退出信息,并释放相关资源。

如果主线程不关心新线程任务执行的情况,不需要获取线程退出信息,并且不想阻塞等待线程退出,那么就可以将线程设置为分离状态,当线程退出时,资源就会被系统自动释放。

thread是要设置成分离状态的线程的tid,该函数既可以新线程自己调,也可以主线程(也可以是其它线程)调,因为它是设置线程的一种状态。

处于分离状态的线程不可被pthread_join,会pthread_join一定会失败,返回22。

//在主线程中调用detach:
pthread_create(&tid, nullptr, threadRoutine, nullptr);
pthread_detach(tid);

//在新线程中调用detach:
pthread_detach(pthread_self());

6.线程取消

线程不仅可以主动return和pthread_exit(),还可以被主进程强制终止

thread是要被终止的进程的tid

  1. 如果线程正常return或者pthread_exit,pthread_join得到的retval就是线程函数的返回值
  2. 如果线程被异常cancel,pthread_join得到的retval是PTHREAD_CANCELED宏,也即-1
  3. 线程被分离了依然可以detach,只不过不能join

五.pthread库

1.pthread库中的线程

 以上使用的线程控制的接口,都不是系统调用,而是原生线程库pthread提供的接口。

再次重申,Linux内核中并没有线程,只有轻量级进程,描述轻量级进程是task_struct(PCB),系统只会提供操作轻量级进程的接口。

我们使用的线程都是用户级线程,所谓“用户”,是因为这些线程都维护在pthread库中,pthread库在地址空间中的共享区,在用户区而非内核区!!!

thread库如何管理线程?先描述,再组织!

pthread库中有描述线程的数据结构,也就是操作系统学科中的TCB。TCB中的属性,一部分来自用户,另一部分来自内核中的轻量级进程。内核中的PCB和库中的TCB是一一对应的关系。

2.系统调用clone

前文说过,一个线程至少要有两种私有的资源:上下文数据和栈

上下文数据被操作系统维护在轻量级进程的PCB中,当线程被调度时,就会将其加载到寄存器。当从CPU上剥离下来时,又会把数据备份到PCB中,这是理所当然的。

但如何理解线程有独立的栈?

地址空间中只有一个栈区,这个栈给谁用呢?

这是一个系统调用,功能就是创建一个轻量级进程,fork的底层就是它,pthread库中的pthread_create就封装了这个接口。

fn是被创建出来的轻量级进程执行的方法;flags是标志位,选择创建一个“真进程”还是一个轻量级进程,即要不要申请地址空间这些资源;stack就是你这个轻量级进程要使用的栈的起始地址。

pthread库在使用这个接口之前,会先malloc一段堆空间,将地址作为参数传给clone,把这块堆空间充当线程的栈。

所以,新线程的栈在库中维护,真正的空间在堆区,由用户提供(因为动态库在用户空间)。地址空间中的栈区由主线程使用,由操作系统提供。类似地,C语言提供了语言层面的缓冲区,这个缓冲区是stdio库维护的。

3.pthread库管理线程

pthread库管理进程的前提是,pthread库映射到进程的地址空间中,进程能访问到库中的代码。线程库是共享的,所以pthread库要管理用户启动的所有线程。

pthread库以数组的形式组织线程,每个单元包含线程TCB,线程局部存储和线程栈。线程tid就是pthread库中描述线程的控制块的地址,使用tid就能直接在地址空间中找到线程

TCB中包含了线程的各种属性,其中一定封装了对应轻量级进程的LWP,未来线程退出的返回值也保存在其中。所以主线程只需访问地址空间中的共享区就能获取返回值,而不用像父进程回收子进程资源那样,必须借助操作系统。

线程栈具体来说是一个指针,指向堆区的某块空间。

线程局部存储与线程独立的资源有关,下文会讲。

4.语言层面的线程库

C++11新增了thread库,实际上这些语言层面的线程库都是封装的原生线程库pthread,编译时必须引入pthread库,否则会出现undefined reference错误。语言层面的线程库是很有意义的,它根据不同平台的提供的接口,对线程进行封装,这样就使得在同一份代码在不同的系统下都可以运行,这提高了代码的可移植性。

5.线程的局部存储

全局变量本身就是被进程内的所有线程共享的,但是在全局变量前,加上__thread这个编译选项,就可以给每个线程私有一份该变量。当pthread库被链接时,编译器就会在线程控制块中开辟出一块空间,将数据拷贝进来。开辟的空间就是局部存储空间。未来线程访问全局变量,实际上访问的是局部存储空间的数据。

注:__thread只能存储内置类型数据,不能存储STL容器。

总结

  1. 结合Linux上的轻量级进程,理解:线程是比进程更加轻量化的一种执行流,线程是在进程内部执行的一种执行流这两句话
  2. 对比进程和线程:进程是资源分配的实体,线程是调度的基本单位
  3. 线程比进程更加轻量化:从创建成本和调度成本两个方面来谈
  4. 线程共享和独立的资源(重点理解栈)
  5. 理解页表转化虚拟地址的过程
  6. pthread库中接口的使用
  7. pthread库的理解

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

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

相关文章

Vessel - Linux hackthebox

#hard #runc #RE #Nodejs-SQLI Enumeration .git leak 使用 dumpall 下载 .git 打开 routes/index.js 可以看到网站使用 nodejs mysql 编写&#xff0c;且只有登录功能 router.post(/api/login, function(req, res) {let username req.body.username;let password req…

深入理解神经网络

图片怎么被识别的过程 (每层神经网络是数组,会对进来的数据进行加权求和[(weight*数据 然后累加) bias])(激活函数是为了训练weight和bias偏移值,在每个神经网络)(分类器会统计概率分类) 2. 引用链接 https://mp.weixin.qq.com/s?__bizMzIyNjMxOTY0NA&mid2247500124&…

springboot256基于springboot+vue的游戏交易系统

游戏交易系统设计与实现 摘 要 在如今社会上&#xff0c;关于信息上面的处理&#xff0c;没有任何一个企业或者个人会忽视&#xff0c;如何让信息急速传递&#xff0c;并且归档储存查询&#xff0c;采用之前的纸张记录模式已经不符合当前使用要求了。所以&#xff0c;对游戏交…

就业班 2401--3.8 Linux Day14--阿帕奇+LNMP(编译安装)

一、WEB服务器 ^世上最重要的事&#xff0c;不在于我们在何处&#xff0c;而在于我们朝着什么方向走。^ 1、WEB服务简介 # 目前最主流的三个Web服务器是Apache、Nginx、 IIS。 - WEB服务器一般指网站服务器&#xff0c;可以向浏览器等Web客户端提供网站的访问&#xff0c;让全…

C语言字符串、字符数组与字符串常量——各种奇奇怪怪的偏难怪

文章目录 一、字符串二、字符数组三、字符串常量&#xff08;字符指针&#xff09;易错点总结 一、字符串 在C语言中&#xff0c;没有专门的字符串变量&#xff0c;没有string类型。 字符串定义有两种方法&#xff1a;一是利用字符数组&#xff0c;二是利用字符指针 。 字符串…

计算机考研|一个很上头的408复习法(可实操)

因为408越复习&#xff0c;越轻松&#xff0c;这就是408上头的原因 408一开始复习肯定是有难度的&#xff0c;这也是408非常劝退的原因之一&#xff0c;但是如果你是科班出身&#xff0c;学完408第一遍之后&#xff0c;你就会轻松很多&#xff0c;因为408的内容虽然多&#xf…

详解数据库、Hive以及Hadoop之间的关系

1.数据库&#xff1a; 数据库是一个用于存储和管理数据的系统。数据库管理系统&#xff08;DBMS&#xff09;是用于管理数据库的软件。数据库使用表和字段的结构来组织和存储数据。关系型数据库是最常见的数据库类型&#xff0c;使用SQL&#xff08;Structured Query Language…

【决策树】预测用户用电量

决策树预测用户用电量 文章目录 决策树预测用户用电量  &#x1f449;引言&#x1f48e;一、 数据预处理数据预处理初步数据分析 二、 机器学习算法决策树回归预测用电量决策树模型介绍&#xff1a;回归预测 三、 可视化结果四、 数据分析与结论代码如下 &#x1f449;引言&a…

如何快速分析OB集群日志,敏捷诊断工具obdiag分析能力实践——《OceanBase诊断系列》之四

1. 前言 obdiag是OceanBase的敏捷诊断工具。1.2版本中&#xff0c;obdiag支持快速收集诊断信息&#xff0c;但仅有收集能力是不够的&#xff0c;还需要有分析能力。因此在obdiag的1.3.0版本中&#xff0c;我们加入了OB集群的日志分析功能。用户可以一键进行集群的OB日志的分析…

arcgis 栅格数据处理2——栅格转地级市(栅格转矢量图)

1. 获取空间分析权限&#xff08;解决无法执行所选工具问题&#xff09; 选中“自定义”中的“扩展模块” 在弹出的模块中选中能选的模块&#xff0c;此处需要选择“spatial analysis”以进行下一步分析 3. 将栅格数据转为整数型&#xff08;解决无法矢量化&#xff09; 选…

弹性布局(下),过渡

弹性布局 1.当子元素在主轴方向的长度和大于父元素的情况 子元素在父元素中放不下是否换行&#xff1f; flex-warp&#xff1a; 默认值&#xff1a; nowrap 不换行&#xff0c;压缩子元素的长度&#xff0c;最常用 可选值&#xff1a; wrap 换行 当子元素被压缩时&#xff0…

AndroidStudio跑马灯实现

在activity_main.xml中编写如下代码&#xff1a; <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_h…

(001)UV 的使用以及导出

文章目录 UV窗口导出模型的主要事项导出时材质的兼容问题unity贴图导出导出FBX附录 UV窗口 1.uv主要的工作区域&#xff1a; 2.在做 uv 和贴图之前&#xff0c;最好先应用下物体的缩放、旋转。 导出模型的主要事项 1.将原点设置到物体模型的底部&#xff1a; 2.应用修改器的…

数字化转型导师坚鹏:科技金融政策、案例及发展研究

科技金融政策、案例及发展研究 课程背景&#xff1a; 很多银行存在以下问题&#xff1a; 不清楚科技金融有哪些利好政策&#xff1f; 不知道科技金融有哪些成功案例&#xff1f; 不知道科技金融未来发展方向&#xff1f; 课程特色&#xff1a; 以案例的方式解读原创方…

15. 三数之和 - 力扣

1. 题目 给你一个整数数组 nums &#xff0c;判断是否存在三元组 [nums[i], nums[j], nums[k]] 满足 i ! j、i ! k 且 j ! k &#xff0c;同时还满足 nums[i] nums[j] nums[k] 0 。请 你返回所有和为 0 且不重复的三元组。 注意&#xff1a;答案中不可以包含重复的三元组。 …

基于springboot+vue的食品安全管理系统(源码+论文)

目录 前言 一、功能设计 二、功能实现 1 首页 2 后台登录 3 食品信息添加页面 4 食品查询 三、库表设计 四、论文 前言 从事食品行业的商家可能会对于食品的储存以及食品的销售&#xff0c;都有着不同门道的想法&#xff0c;那么如何能将这些想法一一实现&#xff0c;…

阿里云一键登录(号码认证服务)

前言 用户登录原来的登录方式如下 1. 手机号验证码 2. 账号密码 运营觉得操作过于复杂, 因此想引入阿里自动登录的逻辑, 也就是号码认证服务,所以才有了这篇问文章 注: 本文只是记录Java端的实现, app端的请自行查询文档实现 官方资料 文档 : 什么是号码认证服务_号码认证服务(…

python的generator生成器用法测试

yield、send、threw、close # coding: utf8# 生成器 def gen(n):for i in range(n):yield ig gen(5) # 创建一个生成器 print(g) # <generator object gen at 0x10bb46f50> print(type(g)) # <type generator># 迭代生成器中的数据(只有执行for循环…

Java代码审计安全篇-常见Java SQL注入

前言&#xff1a; 堕落了三个月&#xff0c;现在因为被找实习而困扰&#xff0c;着实自己能力不足&#xff0c;从今天开始 每天沉淀一点点 &#xff0c;准备秋招 加油 注意&#xff1a; 本文章参考qax的网络安全java代码审计&#xff0c;记录自己的学习过程&#xff0c;还希望…

Unity L屏幕实现方式(已抛弃)

效果 右侧主要的参数&#xff1a;Line参数能够调整中间线的高度&#xff0c;PointXY能够调整整个下方弯曲图像的比例。 使用的是RenderTexture填充RawImage显示的方式&#xff0c;需要将一张RenderTexture设置位摄像机的输出内容。 ShaderGraph 由于这个采用了一定的数学模型…