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. 调度优先级

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

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

相关文章

从百度云网盘下载数据到矩池云网盘或者服务器内

本教程教大家如何快速将百度云网盘数据集或者模型代码文件下载到矩池云网盘或者服务器硬盘上。 本教程使用到了一个开源工具 BaiduPCS-Go&#xff0c;官方地址 &#xff1a; https://github.com/qjfoidnh/BaiduPCS-Go 这个工具可以实现“仿 Linux shell 文件处理命令的百度网…

手机租赁平台开发全攻略打造高效便捷的租赁服务系统

内容概要 手机租赁平台开发&#xff0c;简单说就是让用户能轻松租赁各类手机的高效系统。这一平台不仅帮助那些想要临时使用高端手机的人们节省了不少资金&#xff0c;还为商家开辟了新的收入渠道。随着智能手机的普及&#xff0c;很多人并不需要长期拥有一部手机&#xff0c;…

GitHub 桌面版配置 |可视化界面进行上传到远程仓库 | gitLab 配置【把密码存在本地服务器】

&#x1f947; 版权: 本文由【墨理学AI】原创首发、各位读者大大、敬请查阅、感谢三连 &#x1f389; 声明: 作为全网 AI 领域 干货最多的博主之一&#xff0c;❤️ 不负光阴不负卿 ❤️ 文章目录 桌面版安装包下载clone 仓库操作如下GitLab 配置不再重复输入账户和密码的两个方…

flask后端开发(11):User模型创建+注册页面模板渲染

目录 一、数据库创建和配置信息1.新建数据库2.数据库配置信息3.User表4.ORM迁移 二、注册页面模板渲染1.导入静态文件2.蓝图注册路由 一、数据库创建和配置信息 1.新建数据库 终端中 CREATE DATABASE zhiliaooa DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;2…

OpenAI 12天发布会:AI革命的里程碑@附35页PDF文件下载

在人工智能的浪潮中&#xff0c;OpenAI的12天发布会无疑是2024年科技界的一场盛宴。从12月5日开始&#xff0c;OpenAI连续12天每天发布一个新应用或功能&#xff0c;标志着AI技术的又一次飞跃。本文将梳理这些激动人心的发布&#xff0c;带你一探究竟。 OpenAI发布会概览 Ope…

使 el-input 内部的内容紧贴左边

<el-inputv-model"form.invitor"placeholder"PC端的自动取当前账号的手机号"readonlyclass"no-border-input" />::v-deep(.no-border-input .el-input__inner) { border: none; box-shadow: none; padding-left: 0; /* 确保内容紧贴左边 *…

(南京观海微电子)——GH7009开机黑屏案例分析

一、 现象描述&#xff1a; 不良现象: LVDS模组&#xff0c;开机大概2秒后就黑屏。 二、问题分析 等主机进入Kernel 后做以下测试&#xff1a; 1、手动reset LCM 后 可以显示正常&#xff1b; 总结&#xff1a; 1&#xff09;uboot 部分HS 太窄&#xff0c;仅有4个clk宽度&am…

科技云报到:人工智能时代“三大件”:生成式AI、数据、云服务

科技云报到原创。 就像自行车、手表和缝纫机是工业时代的“三大件”。生成式AI、数据、云服务正在成为智能时代的“新三大件”。加之全球人工智能新基建加速建设&#xff0c;成为了人类社会数字化迁徙的助推剂&#xff0c;让新三大件之间的耦合越来越紧密。从物理世界到数字世…

攻破 kioprix level 4 靶机

又又又来了... 法一、 基本步骤 1.确认主机ip&#xff0c;扫描端口确定服务和版本 2.访问网站&#xff0c;扫描目录&#xff0c;查找敏感信息 3.利用敏感信息和SQL注入进入网站 4.ssh服务连接主机 5.shell逃逸并查找敏感信息&#xff08;与数据库等相关&#xff09; 6.m…

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、…

python报错ModuleNotFoundError: No module named ‘visdom‘

在用虚拟环境跑深度学习代码时&#xff0c;新建的环境一般会缺少一些库&#xff0c;而一般解决的方法就是直接conda install&#xff0c;但是我在conda install visdom之后&#xff0c;安装是没有任何报错的&#xff0c;conda list里面也有visdom的信息&#xff0c;但是再运行代…

玩转影刀AI Power-风景小助手教学

附上该教学网站影刀AI Power帮助文档 - 飞书云文档 影刀AI Power-风景小助手旅游景区大全通过集成AI技术&#xff0c;使得旅游信息的获取和旅游攻略的制定变得更加便捷和个性化&#xff0c;极大地提升了用户的旅游体验。 功能 影刀AI Power-风景小助手旅游景区大全是一款利用…

yolov5 yolov6 yolov7 yolov8 yolov9目标检测、目标分类 目标切割 性能对比

文章目录 YOLOv1-YOLOv8之间的对比如下表所示&#xff1a;一、YOLO算法的核心思想1. YOLO系列算法的步骤2. Backbone、Neck和Head 二、YOLO系列的算法1.1 模型介绍1.2 网络结构1.3 实现细节1.4 性能表现 2. YOLOv2&#xff08;2016&#xff09;2.1 改进部分2.2 网络结构 3. YOL…

电商平台能挡住恶意网络爬虫的攻击吗?

爬虫盗取电商数据的步骤 爬虫技术作为一种数据获取工具&#xff0c;正逐渐成为电商平台的一大隐患。网络爬虫不仅能够获取商家关键信息并滋生仿冒网站&#xff0c;还能收集用户敏感信息&#xff0c;对用户的财产安全和隐私造成严重威胁。同时&#xff0c;爬虫攻击还会扰乱正常…

蓝桥杯速成教程{三}(adc,i2c,uart)

目录 一、adc 原理图​编辑引脚配置 Adc通道使能配置 实例测试 ​编辑效果显示 案例程序 badc 按键相关函数 测量频率占空比 main 按键的过程 显示界面的过程 二、IIC通信-eeprom 原理图AT24C02 引脚配置 不可用状态&#xff0c;用的软件IIC 官方库移植 At24c02手册 ​编辑…

小米汽车加速出海,官网建设引领海外市场布局!

面对国内市场的饱和态势&#xff0c;中国企业出海步伐纷纷加速&#xff0c;小米也是其中的一员。Canalys数据显示&#xff0c;2024年第三季度&#xff0c;小米以13.8%的市场份额占比&#xff0c;实现了连续17个季度位居全球前三的成绩。 据“36 氪汽车”报道&#xff0c;小米汽…

Windows中安装Python3

Windows中安装Python3 1. 下载Python安装包 首先&#xff0c;访问Python的官方网站 Python.org&#xff0c;选择适合你Windows版本的Python安装包。 2. 运行安装包 下载完成.exe文件后&#xff0c;双击运行安装包。在安装过程中&#xff0c;有一些关键的选项需要特别注意&a…

从零搭建SpringBoot3+Vue3前后端分离项目基座,中小项目可用

文章目录 1. 后端项目搭建 1.1 环境准备1.2 数据表准备1.3 SpringBoot3项目创建1.4 MySql环境整合&#xff0c;使用druid连接池1.5 整合mybatis-plus 1.5.1 引入mybatis-plus1.5.2 配置代码生成器1.5.3 配置分页插件 1.6 整合swagger3&#xff08;knife4j&#xff09; 1.6.1 整…

支持最新 mysql9的workbench8.0.39 中文汉化教程来了

之前在 B 站上发布了 mysql8 workbench 汉化教程&#xff0c;一年多来帮助很多初学者解决了不熟悉英文的烦恼。 汉化视频可以访问&#xff1a; 2024最新版mysql8.0.39中文版mysql workbench汉化 中文升级 旧版汉化报错解决_哔哩哔哩_bilibili MySql Workbench汉化_哔哩哔哩_…

JavaWeb期末复习

目录 学习通题目Web技术与应用&#xff08;2024秋&#xff09;第二章 绪论课前测试一.选择题二.填空题 课后巩固 第三章 HTML基础课前测试一.单选题二.填空题 课后巩固一.单选题二.填空题 第四章 CSS课前测试二.单选题三.填空题 4.5 浮动与清理一.单选题 课后巩固二.单选题 第五…