【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库的理解

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

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

相关文章

HelpLook VS GitBook:知识库优劣详解

在信息爆炸的时代&#xff0c;企业要保持竞争优势&#xff0c;就必须善于管理和利用内部的知识资产。企业知识库作为一种集中存储和共享知识的工具&#xff0c;正在成为现代企业不可或缺的一部分。 HelpLook和Gitbook是提供专业知识库的两个平台&#xff0c;也被大众熟知。它们…

C++的一些基础语法

前言&#xff1a; 本篇将结束c的一些基础的语法&#xff0c;方便在以后的博客中出现&#xff0c;后续的一些语法将在涉及到其它的内容需要用到的时候具体展开介绍&#xff1b;其次&#xff0c;我们需要知道c是建立在c的基础上的&#xff0c;所以c的大部分语法都能用在c上。 1.…

C#MQTT编程10--MQTT项目应用--工业数据上云

1、文章回顾 这个系列文章已经完成了9个内容&#xff0c;由浅入深地分析了MQTT协议的报文结构&#xff0c;并且通过一个有效的案例让伙伴们完全理解理论并应用到实际项目中&#xff0c;这节继续上马一个项目应用&#xff0c;作为本系列的结束&#xff0c;奉献给伙伴们&#x…

DDT+yaml实现数据驱动接口自动化

前言 在之前的文章中我们知道了yaml文件可以进行接口自动化。除了yaml文件&#xff0c;Excel文档也可以用来编写自动化测试用例。 一定很想知道这两者有什么区别吧&#xff1f; 1、Excel使用简单&#xff0c;维护难&#xff0c;多种数据类型转换起来比较复杂 2、yaml学习稍…

MySQL通过SQL语句进行递归查询

这里主要是针对于MySQL8.0以下版本&#xff0c;因为MySQL8.0版本出来了一个WITH RECURSIVE函数专门用来进行递归查询的 先看下表格数据&#xff0c;就是很普通的树结构数据&#xff0c;通过parentId关联上下级关系 下面我们先根据上级节点id递归获取所有的下级节点数据&#x…

Jenkins 节点该如何管理?

Jenkins 拥有分布式构建(在 Jenkins 的配置中叫做节点)&#xff0c;分布式构建能够让同一套代码在不同的环境(如&#xff1a;Windows 和 Linux 系统)中编译、测试等 Jenkins 的任务可以分布在不同的节点上运行 节点上需要配置 Java 运行时环境&#xff0c;JDK 版本大于 1.5 节…

2024春招算法打卡-腾讯WXG

大数相乘 class Solution {public String multiply(String num1, String num2) {String ZERO_STR "0";String ONE_STR "1";// 其中一个为0直接返回0if(ZERO_STR.equals(num1) || ZERO_STR.equals(num2)){return ZERO_STR;}// 其中一个为1直接返回另一…

C语言-写一个简单的Web服务器(一)

基于TCP的web服务器 概述 C语言可以干大事&#xff0c;我们基于C语言可以完成一个简易的Web服务器。当你能够自行完成web服务器&#xff0c;你会对C语言有更深入的理解。对于网络编程&#xff0c;字符串的使用&#xff0c;文件使用等等都会有很大的提高。 关于网络的TCP协议在…

zookeeper Study

zk介绍&#xff1b;一种分布式协调服务。 分布式锁&#xff0c;集群选举&#xff0c;数据同步 。 zk都能进行操作&#xff0c;redis&#xff0c;kafka&#xff0c;rabbitmq&#xff0c;都能够用zk做协调管理服务。关键时zk简单操作。 应用说明&#xff1a; 简单介绍一下流程 &…

芯片工程系列(2)传统封装(引线键合与裸片贴装)

英文缩写 Die&#xff1a;即为wafer上切割出来的芯片Wire Bonding&#xff1a;引线键合Dicing&#xff1a;晶圆切割Bias voltage&#xff1a;偏压lead frame&#xff1a;引线框架First Bond&#xff1a;一次键合Second Bond&#xff1a;二次键合PCB&#xff1a;印制电路板&…

JMH287亲测【鸣潮】一键内测风景端V1.0.2已整理并录制视频教学

资源介绍&#xff1a; 否需要虚拟机&#xff1a;否 文件大小&#xff1a;压缩包约15G 支持系统&#xff1a;win7、win10、win11 硬件需求&#xff1a;运行内存16G 4核及以上CPU独立显卡 资源截图&#xff1a; 下载地址&#xff1a; JMH287【鸣潮】一键端 [V1.0.2]

SpringMVC09、Ajax

9、Ajax 9.1、简介 AJAX Asynchronous JavaScript and XML&#xff08;异步的 JavaScript 和 XML&#xff09;。 AJAX 是一种在无需重新加载整个网页的情况下&#xff0c;能够更新部分网页的技术。 Ajax 不是一种新的编程语言&#xff0c;而是一种用于创建更好更快以及交互…

Spring Security的API Key实现SpringBoot 接口安全

Spring Security的API Key实现SpringBoot 接口安全 Spring Security 提供了各种机制来保护我们的 REST API。其中之一是 API 密钥。API 密钥是客户端在调用 API 调用时提供的令牌。 在本教程中&#xff0c;我们将讨论如何在Spring Security中实现基于API密钥的身份验证。 API…

CSP初赛备考—汉字与运算

汉字 英文字符 英文字符的编码有两种:①ASCII标准码,7位(128个字符)②ASCII扩展吗,8位(256个字符) 中文字符 汉字分为两级:①一级汉字:3755个,按汉语拼音字母的次序排列。②二级汉字:3008个,按偏旁部首排列。 那么,怎么编码呢?要使用区位码和字形码等等。 区…

【UE5】游戏框架GamePlay

项目资源文末百度网盘自取 游戏框架 游戏 由 游戏模式(GameMode) 和 游戏状态(GameState) 所组成 加入游戏的 人类玩家 与 玩家控制器(PlayerController) 相关联 玩家控制器允许玩家在游戏中拥有 HUD&#xff0c;这样他们就能在关卡中拥有物理代表 玩家控制器还向玩家提供 …

在centos7系统中如何给docker配置代理

一、需求场景 生产环境私有云中&#xff0c;通常一个集群的机器中只有几台机器可以直接访问公网&#xff0c;其他机器需要通过代理的方式从能访问公网的机器出去&#xff0c;在已经做了如下配置之后&#xff0c;使用docker pull命令已经报错超时timeout&#xff0c;这时可以尝…

利用tree命令自动保存文件层级结构

tree命令的使用 为了将上图左侧的文件目录&#xff0c;生成上图右侧中的文件夹结构列表&#xff0c;保存在txt中&#xff0c;使用了如下cmd命令&#xff1a; C:\armadillo-12.8.0>tree .>list.txt以上tree命令分为3部分&#xff1a; tree 命令. 在当前目录>list.tx…

02hadoop伪分布式搭建

3. 环境安装 3.1 安装方式 单机模式 只能启动MapReduce 伪分布式 能启动HDFS、MapReduce 和 YARN的大部分功能 完全分布式 能启动Hadoop的所有功能 3.2 安装JDK 3.2.1 JDK安装步骤 下载JDK安装包&#xff08;下载Linux系统的 .tar.gz 的安装包&#xff09; https://www…

循序渐进丨MogDB 数据库特性之动态数据脱敏机制

数据脱敏是行之有效的数据库隐私保护方案之一&#xff0c;可以在一定程度上限制非授权用户对隐私数据的窥探。动态数据脱敏机制是一种通过定制化脱敏策略来实现对隐私数据保护的技术&#xff0c;可以在保留原始数据的前提下有效地解决非授权用户对敏感信息访问的问题。当管理员…

稀碎从零算法笔记Day10-LeecCode:赎金信

题型&#xff1a;哈希表、字符串 链接&#xff1a;383. 赎金信 - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1a;LeetCode 题目描述 给你两个字符串&#xff1a;ransomNote 和 magazine &#xff0c;判断 ransomNote 能不能由 magazine 里面的字符构成。 如果可以…