Linux系统编程——线程

目录

一、前言

二、线程

1、线程的理解

三、线程相关的接口

1、线程的创建

2、线程的等待

3、实验

四、总结

1、线程优点

2、线程缺点

3、线程异常

4、Linux下的进程与线程对比


一、前言

       之前的文章中我们已经对进程相关的概念做了认识,从创建进程、子进程,进程回收、进程替换等,由于我们之前对于知识框架的不熟悉,为了方便我们的理解学习,我们在之前的学习中没有做到深挖细节和深入理解,对之前的进程内容的有些部分做了简单的抽象,接下来我们学习的是比进程粒度更细的一个概念——线程

       在这部分内容中,我们除了学习主线的内容外,还会对之前的內容稍作补充和修改,这都是会在文章中提到的。

二、线程

线程和进程都是操作系统中的两种基本的执行单位,概念上有本质的区别,但在不同平台和操作系统的实现中,它们之间的界限可能并不那么明显。

  • 在某些操作系统(尤其是传统的Unix类系统,如Linux、macOS等)中,线程的管理是由内核直接负责的,线程和进程的区别非常清晰。操作系统会为每个线程提供独立的调度和执行机制。
  • 在一些早期的操作系统或特定的嵌入式平台上,线程的管理可能是由用户空间的库来完成的,操作系统本身并不直接感知线程的存在。在这种情况下,线程和进程的区分可能显得不那么重要,因为操作系统本质上只是管理进程。

1、线程的理解

  1. 线程是在进程内部的执行流
  2. 线程相比进程,粒度更细,调用的成本更低
  3. 线程是CPU调度的基本单位

我们先通过具体的例子简单认识一下线程

        在我们讲进程的那一篇文章中,我们提到过  进程=PCB+被加载到内存中的数据 对于单个进程来讲进程的PCB和程序中的数据是通过进程地址空间和页表之间的映射才能获取的。CPU实际上是通过访问进程的PCB来实现对进程的调度的

        在我们创建子进程的时候,我们知道创建的子进程在未对父进程的数据进行修改时,为了节省存储空间,创建出的子进程的程序地址空间和页表会指向和父进程一样的数据和代码,而且,我们可以 通过fork()接口的返回值的判断,让父子进程执行不同的代码所以不同的执行流,可以做到执行不同的资源,即可以做到对特定资源的划分

所以在下次创建进程的时候,操作系统并不创建有关进程的所有结构,而是 只创建PCB,将新的PCB指向已经存在的进程。如下

接着以子进程划分程序资源类似的手段将进程的代码区划分为不同的区域,并将不同的PCB设置为实际分别负责执行不同的区域的代码。

最终,不同的PCB可以访问进程地址空间内代码区的不同区域,并通过相应的页表来访问到实际的物理内存。

所以就这样在进程内部创建了多个PCB执行流,而每个PCB执行流都只能访问一小部分代码和对应的一小部分页表,那么在Linux操作系统中,我们就将这样的PCB执行流称为“线程”

上述只是介绍了以下Linux操作系统中,线程的 粗粒度 的原理。 


       基于上述的介绍,我们知道了实际上在Linux操作系统中,进程和线程是一对多的关系即 进程:线程=1:N

       我们知道操作系统对于进程的管理是通过维护各自的 PCB 将对应进程的所有属性描述组织起来,那么对于在操作系统中存在的更多的线程,毫无疑问,操作系统也需要用一个结构体将他们描述、管理、组织起来。在大多数的操作系统中,描述线程的结构体被称为 TCB

       如果一个操作系统,为了描述管理进程和线程,在内核中分别实现了不同的PCB和TCB,那么PCB和TCB之间一定存在着非常复杂的耦合关系,因为PCB描述的是一个进程,而TCB描述的是进程内部更细小的线程,所以说 这两部分之间一定存在着相当一部分重叠的属性。

       所以说在维护一个进程与其内部的线程之间的关系时,一定是一个非常复杂的过程。


        在文章刚开始我们就提到过了,不同的操作系统对于进程和线程的区分的界限是不同的,我们上面所介绍的例子都是在Linux操作系统下,不同的操作系统其线程的实现方式也是不同的。

        我们在上面说到过操作系统会为线程维护一个结构体,但是实际情况是并不是所有的操作系统都会这么做,这会使得TCB和PCB之间的关系非常复杂。

        在Linux操作系统中就没有另外实现一个描述线程的结构体,而是使用了 task_struct(进程的PCB)模拟了线程,即在Linux中描述进程和线程的实际上是一个结构体: task_struct

我们使用的Windows操作系统才是真正的将进程和线程分开,分别实现了PCB和TCB以分别用来维护线程和进程,这样的被称为 真线程操作系统

但是为什么不同的操作系统会对进程和线程之间的关系, 设计出这样的差别呢?其实是开发者对 进程和线程在执行流层面的理解不同.

  1. 以 Windows 来说, Win为了维护线程真正实现了一个不同于PCB的TCB. 也就是说, Win的开发者认为进程和线程在执行流层面是不同的东西. 进程有自己的执行流, 线程在进程内部也有自己的执行流
  2. 而 Linux 则认为 进程和线程在概念上不做区分, 都是执行流. PCB要不要被CPU调度?TCB要不要被CPU调度?PCB调度要不要优先级?TCB要不要?要不要通过PCB找到代码和数据?要不要通过TCB找到代码和数据?进程切换要不要保护进程的上下文数据?线程切换要不要保护上下文数据?……在Linux看来, 种种迹象表明 PCB和TCB的功能 不从更细节来细分的话, 其实是大致相同的. 无非就是PCB和TCB中描述的代码量和数据量的不同, 所以 进程和线程都只看成一个执行流.

这么做的好处是:

用进程PCB模拟实现线程, 对线程 可以复用操作系统中已经针对进程实现的各种调度算法, 因为进程和线程的描述结构是相同的.

也不用维护进程和线程之间的关系.

也就是说, Linux操作系统中 线程TCB底层就可以看作进程PCB

TipsLinux复用PCB实现TCB, 那么从CPU的角度看待线程, 其实与进程没有区别. CPU调度线程实际上看到的还是PCB(task_strcut)

虽说Linux操作系统中的线程使用了进程的PCB模拟实现的,但是其在设计时已经考虑到了线程,也就是说在PCB内部其实是有用来表示线程的结构的

thread_struct{}结构体内部存储的大部分都是寄存器相关信息. 与维护不同线程的上下文数据有关系 


既然上面已经提到了Linux操作系统的线程也是用task_struct来实现的,那么我们现在该如何理解进程呢?

此时我们就需要改变一下我们之前对进程的认识了不能单纯只认为进程就是一个PCB和代码数据的和,而是如下图中所包含的所有结构合起来才能称为进程。

        在我们知道线程之前,我们所理解的进程就是只有一个执行流,即只有一个task_struct,但是现在我们将只包含一个执行流的进程称为 单执行流进程,称 内部存在多个执行流的进程为 多执行流进程

        所以现在我们返回来再次理解task_struct时,我们会知道我们当前知道的task_struct比之前所认识的task_struct体量要小,因为我们理解现在CPU所看到的task_struct可能是线程,即 轻量化的进程

 学到了这里我们可以说:

  1. 进程是承担操作系统 资源分配 的基本实体,即进程是向系统申请资源的基本单位。
  2. CPU调度是通过PCB(task_struct)调度的,所以线程是CPU调度的基本单位

总结:

  • 线程是进程内部运行的执行流,只访问执行进程的一部分代码和数据。
  • 线程和进程相比粒度更细、调用成本更低,进程切换调度需要切换PCB、进程地址空间、页表等。而线程切换调度,只需要切换TCB(在Linux下还是PCB)
  • 线程是CPU调度的基本单位 

三、线程相关的接口

1、线程的创建

线程的创建和查看也有系统的调用接口:pthread_create

该接口的作用是创建一个新线程

  • 第一个参数是此类型的指针,是一个输出型参数,用于获取创建的线程的id
  • 第二个参数是线程属性结构体的指针,现在我们只需要知道传入的是nullptr
  • 第三个参数是一个函数指针,参数和返回值都是空指针,用于传入此线程需要执行的函数
  • 第四个参数是一个空指针,其实就是第三个参数(函数指针)所指向的函数的参数

2、线程的等待

与子进程一样,线程也需要等待,pthread_join

该接口的作用是 join一个终止的进程 ,即等待指定的终止的线程。

  • 第一个参数需要传入的是 需要等待的线程的id
  • 第二个参数接收线程退出的结果,暂时不关心它,我们先看实验现象

3、实验

#include<iostream>
#include<pthread.h>
#include<unistd.h>
#include<string>using std::cout;
using std::endl;
using std::cerr;
using std::string;void* pthreadFun1(void* argc){//定义线1执行的函数string str=(char*)argc;while(true){cout<<str<<"My pid::"<<getpid()<<endl;sleep(1);}}
void* pthreadFun2(void* argc){//定义线程2执行的函数string str=(char*)argc;while(true){cout<<str<<"My pid::"<<getpid()<<endl;sleep(1);}
}int main()
{pthread_t pth1,pth2;pthread_create(&pth1,nullptr,pthreadFun1,(void*)"I am pthread1!");pthread_create(&pth2,nullptr,pthreadFun2,(void*)"I am pthread2!");sleep(1);while(true){cout<<"Main pthread has gone!"<<"My pid::"<<getpid()<<endl;sleep(1);}pthread_join(pth1,nullptr);pthread_join(pth2,nullptr);return 0;
}

在编译生成可执行函数的时候特别要注意的是  我们需要手动链接 pthread库 ,因为其是第三方库

我们可以在进程运行的时候查看系统的进程表,如下 

 可以看到相关的进程只有一个,当然除了查看进程,线程也有查看的方法 ps -aL(a:all,L:轻量级进程)

可以看到线程列表中存在着三个相同的pid的线程,这三个线程同时属于一个 PID31660, 同时他们还有着自己的 LWP 轻量级进程编号

有一个的PID和LWP是相同的,这个线程是主线程。

四、总结

1、线程优点

  • 资源共享:同一个进程中的所有线程共享该进程的资源,包括全局变量、堆栈和文件描述符。这使得线程之间的通信和数据交换更加高效。

  • 创建和切换开销小:相比于进程,线程的创建和销毁成本更低,因为不需要像进程那样分配独立的地址空间和其他资源。同样,线程之间的上下文切换也比进程间的切换更快。

  • 简化编程模型:对于某些类型的应用程序,尤其是那些需要并行执行多个任务的应用程序,使用线程可以简化编程逻辑,使代码更易于理解和维护。

  • 充分利用多核处理器:现代计算机通常具有多核CPU,线程可以通过并行运行来充分利用这些硬件资源,从而提高应用程序的性能。比如一个程序运行时, 需要等待操作系统和网卡之间的I/O操作, 又要等待操作系统和磁盘之间的I/O操作.如果单线程的话, 这两个I/O操作只能一个一个等, 不过, 如果是多线程的话就可以同时等待不用排队.

  • 细粒度控制:线程允许对每个任务进行更细粒度的控制,例如可以单独设置优先级或调度策略。

  • 对于计算密集型应用, 为了能在多处理器系统上运行, 会将计算分解到多线程去实现。

2、线程缺点

  1. 线程安全问题:由于线程共享相同的地址空间,如果一个线程修改了共享的数据结构,而没有适当的同步机制,可能会导致其他线程看到不一致的状态,产生竞态条件(race condition)。

  2. 调试困难:线程错误往往难以重现和诊断,因为它们依赖于特定的执行顺序和时间点。此外,调试工具可能无法很好地支持多线程环境下的调试。

  3. 死锁风险:当两个或更多的线程相互等待对方持有的资源时,会发生死锁。设计良好的同步机制和避免循环等待是防止死锁的关键。

  4. 增加复杂性:虽然线程可以简化一些编程场景,但引入多线程也会增加程序的复杂性,特别是在处理同步和通信方面。

  5. 受限于单进程限制:线程属于同一个进程,因此受到操作系统对该进程施加的任何限制的影响,比如打开文件的数量或者可用内存大小。

  6. 非阻塞操作的需求:在一个线程中执行长时间运行的操作(如I/O操作)会阻塞整个线程,影响其他线程的执行效率。为了解决这个问题,通常需要采用异步I/O或者其他非阻塞技术。

3、线程异常

一个多线程进程中, 虽然一般每个线程访问执行的代码和数据不同, 但这些代码和数据都是属于整个进程的, 只有一份.

如果线程出现了异常, 那就说明什么?就说明是进程某处代码出现了异常.

也就是所, 线程出现异常是会影响整个进程 的. 线程出现异常其实就是进程出现了异常.

线程出现异常, 操作系统就会像线程发送信号, 然后会将整个进程终止. 整个进程终止, 进程中的其他所有线程也会退出.

4、Linux下的进程与线程对比

  • 进程是系统资源分配的基本单位
  • 线程是调度的基本单位
  • 进程和线程共享的资源
  1. 内存地址空间:进程中的所有线程共享同一块虚拟内存地址空间。这意味着它们可以访问相同的全局变量、静态数据以及动态分配的内存(如通过malloc()new操作符分配的堆内存)。这也使得线程间的通信变得简单高效。
  2. 文件描述符:包括打开的文件、套接字、管道等在内的所有文件描述符都是由进程创建并管理的,因此所有线程都可以读写这些文件描述符。这包括标准输入输出流(stdin, stdout, stderr)以及其他任何已打开的文件或网络连接。
  3. 打开的设备:如果进程打开了某些硬件设备(如打印机、串行端口等),那么所有线程都能够与这些设备进行交互。
  4. 信号处理函数:所有线程共享同一组信号处理器。当一个信号被递送给进程时,它会被传递给其中一个没有屏蔽该信号的线程执行相应的信号处理程序。
  5. 环境变量
  6. 当前工作目录
  7. 用户ID和组ID
  • 多线程共享进程数据,不过不同的线程也有着自己的一份数据
  1. 线程id:在Linux系统中,每个线程确实有其自己的标识符。这个标识符被称为轻量级进程(Light Weight Process, LWP)ID,有时也被称作线程ID(TID, Thread ID)。LWP和传统的“进程”概念不同,它是操作系统内核用来管理线程的一种方式。
  2. 一组寄存器:每个线程都有自己的一组寄存器用来维护线程的上下文数据。为了确保当操作系统在多个线程之间切换时,每个线程都能保持其执行状态,并且可以在被重新调度时从上次中断的地方继续执行。
  3. 线程栈:每个进程在运行时都会有自己的栈结构,用于管理函数调用、局部变量、参数传递和返回地址等。
  4. 信号屏蔽字:线程异常 就是 进程异常. 线程异常操作系统会向线程发送信号,虽然线程和进程在信号处理上有共享的部分,但每个线程都有自己独立的信号屏蔽机制,这使得它们可以在一定程度上控制信号的接收行为。这种设计既保持了进程级别的统一性,又给予了线程一定的灵活性。
  5. 调度优先级

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

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

相关文章

SD ComfyUI工作流 对人物图像进行抠图并替换背景

文章目录 人物抠图与换背景SD模型Node节点工作流程工作流下载效果展示人物抠图与换背景 此工作流旨在通过深度学习模型完成精确的人物抠图及背景替换操作。整个流程包括图像加载、遮罩生成、抠图处理、背景替换以及最终的图像优化。其核心基于 SAM(Segment Anything Model)与…

微服务-1 认识微服务

目录​​​​​​​ 1 认识微服务 1.1 单体架构 1.2 微服务 1.3 SpringCloud 2 服务拆分原则 2.1 什么时候拆 2.2 怎么拆 2.3 服务调用 3. 服务注册与发现 3.1 注册中心原理 3.2 Nacos注册中心 3.3 服务注册 3.3.1 添加依赖 3.3.2 配置Nacos 3.3.3 启动服务实例 …

02-18.python入门基础一基础算法

&#xff08;一&#xff09;排序算法 简述&#xff1a; 在 Python 中&#xff0c;有多种常用的排序算法&#xff0c;下面为你详细介绍几种常见的排序算法及其原理、实现代码、时间复杂度以及稳定性等特点&#xff0c;并对比它们适用的场景。 冒泡排序&#xff08;Bubble Sor…

深度学习blog-卷积神经网络(CNN)

卷积神经网络&#xff08;Convolutional Neural Network&#xff0c;CNN&#xff09;是一种广泛应用于计算机视觉领域&#xff0c;如图像分类、目标检测和图像分割等任务中的深度学习模型。 1. 结构 卷积神经网络一般由以下几个主要层组成&#xff1a; 输入层&#xff1a;接收…

三维扫描在汽车/航空行业应用

三维扫描技术应用范围广泛&#xff0c;从小型精密零件到大型工业设备&#xff0c;都能实现快速、准确的测量。 通过先进三维扫描技术获取产品和物体的形面三维数据&#xff0c;建立实物的三维图档&#xff0c;满足各种实物3D模型数据获取、三维数字化展示、3D多媒体开发、三维…

【Axure视频教程】中继器表格间传值

今天教大家在Axure制作中继器表格间传值的原型模板&#xff0c;可以将一个中继器表格里的行数据传递到另外一个中继器表格里&#xff0c;包括传值按钮在中继器内部和外部两中案例。 这个原型模板是用中继器制作的&#xff0c;所以使用也很简单&#xff0c;只需要在中继器表格里…

【测试】接口测试

长期更新好文&#xff0c;建议关注收藏&#xff01; 目录 接口规范接口测试用例设计postmanRequests 复习HTTP超文本传输协议 复习cookiesession 实现方式 1.工具 如postman ,JMeter&#xff08;后者功能更全&#xff09; 2.代码 pythonrequests / javahttpclient【高级】 接…

目录 1、常用系统数据类型 1. int或integer 2. tinyint 3. decimal[(p[,s])]或numeric[(p[,s])] 4. char(n) 5. varchar(n|max) 6. datetime 2、T-SQL创建表 3、T-SQL修改表 4、T-SQL表数据的操作 4.1 插入数据 4.2 修改数据 4.3 删除数据 5、删除表 1、常用系统…

【LLM】OpenAI 的DAY12汇总和o3介绍

note o3 体现出的编程和数学能力&#xff0c;不仅达到了 AGI 的门槛&#xff0c;甚至摸到了 ASI&#xff08;超级人工智能&#xff09;的边。 Day 1&#xff1a;o1完全版&#xff0c;开场即巅峰 12天发布会的开场即是“炸场级”更新——o1完全版。相比此前的预览版本&#x…

Redis缓存知识点汇总

Redis缓存知识点汇总 请先思考如下问题 1.Redis的缓存击穿&#xff0c;穿透&#xff0c;雪崩是什么意思&#xff1f;原因和解决方案有哪些&#xff1f; 2.Redis支持宕机数据恢复&#xff0c;他的持久化方式及其原理是什么&#xff1f; 3.如何保证双写一致性&#xff0c;即如何保…

Gitlab17.7+Jenkins2.4.91实现Fastapi/Django项目持续发布版本详细操作(亲测可用)

一、gitlab设置&#xff1a; 1、进入gitlab选择主页在左侧菜单的下面点击管理员按钮。 2、选择左侧菜单的设置&#xff0c;选择网络&#xff0c;在右侧选择出站请求后选择允许来自webhooks和集成对本地网络的请求 3、webhook设置 进入你自己的项目选择左侧菜单的设置&#xff…

仓颉编程笔记1:变量函数定义,常用关键字,实际编写示例

本文就在网页版上体验一下仓颉编程&#xff0c;就先不下载它的SDK了 基本围绕着实际摸索的编程规则来写的 也没心思多看它的文档&#xff0c;写的不太明确&#xff0c;至少我是看的一知半解的 文章提供测试代码讲解、测试效果图&#xff1a; 目录 仓颉编程在线体验网址&…

Linux 文件 I/O 基础

目录 前言 一、文件描述符&#xff08;File Descriptor&#xff09; 二、打开文件&#xff08;open 函数&#xff09; 三、读取文件&#xff08;read 函数&#xff09; 四、写入文件&#xff08;write 函数&#xff09; 五、关闭文件&#xff08;close 函数&#xff09; …

Vue项目中env文件的作用和配置

在实际项目的开发中&#xff0c;我们一般会经历项目的开发阶段、测试阶段和最终上线阶段&#xff0c;每一个阶段对于项目代码的要求可能都不尽相同&#xff0c;那么我们如何能够游刃有余的在不同阶段下使我们的项目呈现不同的效果&#xff0c;使用不同的功能呢&#xff1f;这里…

20241130 RocketMQ本机安装与SpringBoot整合

目录 一、RocketMQ简介 ???1.1、核心概念 ???1.2、应用场景 ???1.3、架构设计 2、RocketMQ Server安装 3、RocketMQ可视化控制台安装与使用 4、SpringBoot整合RocketMQ实现消息发送和接收? ? ? ? ? 4.1、添加maven依赖 ???4.2、yaml配置 ???4.3、…

“宠物服务的跨平台整合”:多设备宠物服务平台的实现

2.1 SSM框架介绍 本课题程序开发使用到的框架技术&#xff0c;英文名称缩写是SSM&#xff0c;在JavaWeb开发中使用的流行框架有SSH、SSM、SpringMVC等&#xff0c;作为一个课题程序采用SSH框架也可以&#xff0c;SSM框架也可以&#xff0c;SpringMVC也可以。SSH框架是属于重量级…

Word表格另起一页解决办法

Word表格另起一页解决办法 表格设置根据内容自动调整&#xff0c;取消指定高度第1步 第2步

iOS Masonry对包体积的影响

01 Masonry介绍 Masonry是iOS在控件布局中经常使用的一个轻量级框架&#xff0c;Masonry让NSLayoutConstraint使用起来更为简洁。Masonry简化了NSLayoutConstraint的使用方式&#xff0c;让我们可以以链式的方式为我们的控件指定约束。 常用接口声明与实现&#xff1a; 使用方式…

抖去推碰一碰系统技术源码/open SDK转发技术开发

抖去推碰一碰系统技术源码/open SDK转发技术开发 碰一碰智能系统#碰碰卡系统#碰一碰系统#碰一碰系统技术源头开发 碰碰卡智能营销系统开发是一种集成了人工智能和NFC技术的工具&#xff0c;碰碰卡智能营销系统通过整合数据分析、客户关系管理、自动化营销活动、多渠道整合和个…

JS中的闭包和上下文

变量提升 和 函数提升 这里要提到一个提升的概念&#xff0c;即在JS中&#xff0c;在解析代码之前还有一个预处理的过程&#xff0c;这个过程中会把部分变量和函数声明提前到代码的最顶部&#xff0c; 会在其他所有代码之前执行。虽然当我们按照规范&#xff08;严格模式或者T…