Linux之线程细究~~~~

本文详细探讨了Linux下线程与进程的关系,解释了线程如何通过进程模拟实现,以及内核线程的实现原理。文章还对比了线程与进程的特点,包括资源共享与独立性,创建和销毁的代价,以及它们在多任务处理中的应用场景。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

线程

一、线程基本概念
  首先要说的就是在我们Linux下并不存在真正的线程,在Linux下,线程采用进程模拟实现的。当我们在单个进程中需要处理多个任务时,又不能创建多个进程,这时我们就引入了线程的概念。在Linux下,由于线程是用进程实现的,所以我们也罢线程叫做 轻量级进程 (LWP:light weight process),它的本质仍是进程。进程其实就是一个线程组,其中包含一个或者多个线程。
  
二、Linux下内核线程的实现原理
  在类Unix系统中,早期是没有线程的概念的,在80年代左右才引进这个概念,它是利用进程实现线程的概念的。所以进程与线程是密切相关的。

  1. 轻量级进程(light-weight process),也有PCB,创建线程使用的底层函数和进程一样,都是clone
  2. 从内核里看进程和线程是一样的,都有各自不同的PCB,但是PCB中指向内存资源的三级页表是相同的
  3. 进程可以蜕变成线程
  4. 线程可看做寄存器和栈的集合
  5. 在linux下,线程最是小的执行单位;进程是最小的分配资源单位
    在这里插入图片描述

注:三级映射:进程PCB->页目录->页表->物理页面->内存单元

对于进程来说,相同的一块虚拟地址在不同的进程中反复使用而不冲突,原因就是虽然它们是同一块虚拟地址,但是它们的页目录,页表,物理页面各不相同,最终映射到不同的物理页面内存单元,最终访问不同的物理页面。但是线程不同!两个线程具有各自独立的PCB,但共享同一个页目录,也就共享同一个页表和物理页面。所以两个PCB共享一个地址空间。实际上,无论是创建进程的fork,还是创建线程的pthread_create,底层实现都是调用同一个内核函数clone。如果复制对方的地址空间,那么就产出一个“进程”;如果共享对方的地址空间,就产生一个“线程”。因此:Linux内核是不区分进程和线程的。只在用户层面上进行区分。所以,线程所有操作函数 pthread_* 是库函数,而非系统调用。

三、同一个进程下线程共享与独有的数据
  在讨论这个问题之前,我们还得回顾一下进程的一个知识点。我们知道,在创建进程的的时候,我们大部分用的是fork()函数,其实还有一个函数就是vfork()函数,vfork()函数创建的子进程与父进程共用同一块虚拟地址空间,它存在的意义就是快速的创建子进程,并且子进程是专门用来运行其它程序的,共用地址空间可以减少子进程数据拷贝父进程的消耗,因此速度快。但它有一个缺陷就是若子进程 return 后,有可能会造成父进程的程序调用栈混乱,这种情况是我们不愿意看到的。看到这里我想你应该知道我要说什么了。那就是多线程共享一块虚拟地址空间与vfork()函数一样,那么它是如何避免这个问题的呢?那就是通过线程之间共享与独有的数据。

共享

  1. 内存地址空间 (.text/.data/.bss/heap/共享库)
  2. 文件描述符表
  3. 信号的处理方式
  4. 当前工作目录
  5. 用户ID与组ID

独有

  1. 线程ID
  2. 栈空间
  3. errno变量
  4. 信号屏蔽字
  5. 调度优先级
  6. 寄存器

线程控制原语

注意:使用线程库时gcc指定 –lpthread

  1. 线程控制
    int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine) (void *), void *arg);
    返回值:成功:0; 失败:错误号 -----Linux环境下,所有线程特点,失 败均直接返回错误号。
    参数: pthread_t:当前Linux中可理解为:typedef unsigned long int pthread_t;
    参数1:传出参数,保存系统为我们分配好的线程ID
    参数2:通常传NULL,表示使用线程默认属性。若想使用具体属性也可以修改该参数。
    参数3:函数指针,指向线程主函数(线程体),该函数运行结束,则线程结束。
    参数4:线程主函数执行期间所使用的参数。

  2. 线程终止
    ~ 从线程主函数return。这种方法对主控线程不适用,从main函数return相当于调用exit。
    ~ 一个线程可以调用pthread_cancel终止同一进程中的另一个线程。
    int pthread_cancel(pthread_t thread); 成功:0;失败:错误号
    注意】:线程的取消并不是实时的,而有一定的延时。需要等待线程到达某个取消点(检查点)。
    类似于玩游戏存档,必须到达指定的场所(存档点,如:客栈、仓库、城里等)才能存储进度。杀死线程也不是立刻就能完成,必须要到达取消点。
    取消点:是线程检查是否被取消,并按请求进行动作的一个位置。通常是一些系统调用creat,open,pause,close,read,write… 执行命令man 7 pthreads可以查看具备这些取消点的系统调用列表。
    ~ 线程可以调用pthread_exit终止自己。
    void pthread_exit(void *retval); 参数:retval表示线程退出状态,通常传NULL

  3. 线程等待:获取指定线程的返回值,并且允许操作系统回收线程资源,一个线程默认启动后处于joinable状态,处于这个状态的线程退出时不会自动释放资源。
    int pthread_join(pthread_t thread, void **retval); 成功:0;失败:错误号
    参数:thread:线程ID (【注意】:不是指针);retval:存储线程结束状态。

  4. 线程分离:分离一个线程,线程退出后系统自动释放资源,被分离的线程无法被等待。网络、多线程服务器常用。 int pthread_detach(pthread_t thread); 成功:0;失败:错误号

多进程与多线程

敲黑板啦。。既然多进程多线程都可以并发完成任务,哪个好?或者说,既然多进程可以完成,那么为什么还要引入多线程呢?上图~~

在这里插入图片描述
由于线程共用一个地址空间,所以线程间通信极为方便,相比于进程而言,线程的创建以及销毁的代价相对较低,进程它是一个线程组,那么对于线程而言,它的执行粒度更为细致。但同时它缺乏访问控制,健壮性低,像一些系统调用都是针对进程的,编写与调试一个多线程程序比单线程程序困难得多,因为线程没有内存隔离,如果单个线程出现问题,那么有可能导致整个程序的退出。

多线程与多进程的应用场景

  1. 需要频繁创建的销毁的优先使用线程,例如:web服务器。建立一个线程,断了就销毁线程。如果用进程,创建和销毁的代价是很难承受的。
  2. 如果是CPU密集型程序优先使用线程,程序中都是大量的运算,那么它比较消耗CPU,切换比较频繁,使用线程比较合适。例如:图像处理,算法处理。
  3. 强相关的程序处理用线程,弱相关的程序处理用进程。例如:一般的server需要完成如下任务:消息收发和消息处理。消息收发和消息处理就是弱相关的任务,而消息处理里面可能又分为消息解码、业务处理,这两个任务相对来说相关性就要强多了。因此消息收发和消息处理可以分进程设计,消息解码和业务处理可以分线程设计。
  4. 可能扩展到多机分布的用进程,多核分布的用线程。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值