【Linux进程VS线程】

本文从以下几个方面阐述进程和线程之间的区别

  1. 二者相同点
  2. 实现方式的差异
  3. 实体通信方式的不同
  4. 控制方式的异同
  5. 多线程/进程上下文切换开销问题

二者相同点

无论是进程还是线程,对于我们程序员而言都是实现多任务并发的技术手段。二者都可以独立调度,因此在多任务环境下,功能上并无差异。二者所具有的状态都非常相似。子进程/线程的调度 一般与父进程/线程平等竞争。

在Linux内核2.4版本之前,线程的实现和管理方式就是完全按照进程方式实现的,在内核2.6版本之后才有了单独的线程实现

实现方式的差异

进程是资源分配的基本单位,线程是调度的基本单位。

这句经典名言,哪怕随便拉一个同学,即使他C语言挂科了也能回答上来这句话。这就是二者最显著的区别。

解读一下这句话就是,只要达到线程的水平就可以被调度了(进程自然也就能被调度了);但分配资源的对象必须是进程,并不会给一个线程单独分配资源。若要执行一个任务,想要获取资源,起码得有进程,其他子任务可以以线程的身份运行,共享资源就行了。其实无需过度纠结,也没有人写出只有线程没有进程的程序,多线程中至少都有一个主线程,而这个主线程其实就是main函数的进程

简而言之,进程的个体间是完全独立的,在进程创建之初操作系统会为每个进程分配独立的虚拟内存空间;而线程间是彼此依存的。多进程环境中,任意一个进程的终止,不会影响到其他进程;而多线程环境中,父线程终止了,全部子线程被迫终止(没有资源了),而任意一个子线程终止了,一般不会影响到其他线程,除非子线程执行exit()系统调用,此时全部线程灭亡。

从实现角度讲:

进程的实现调用的是fork()系统调用;线程的实现调用的是clone()系统调用;

其中fork(),是将父进程的全部资源复制给我子进程,这样说也不对,因为有写实拷贝技术的存在----父子进程内存共享,只有父或子进程需要修改页面的数据的时候才会复制内存。延迟甚至免除fork()对页面的复制(以页为单位复制,因为操作系统分配管理内存时是以页为单位),提高fork复制的效率。而clone()只复制了一小部分必要的资源。我们在创造线程的时候,最广泛使用的因该是 POSIX线程库中的pthread_create()。

实体通信方式的不同

进程间的通信方式:A.信号量  B.消息队列  C.共享内存  D.管道(有名/无名)   E.信号  F.socket

线程间的通信方式:A.信号量  B.互斥锁  C.读写锁  D.自旋锁  E.条件变量

值得注意的是:

信号量的“双重身份”线程通信的信号量和进程间的信号量是有区别的

进程需通过特殊机制(如shmget)共享内存,而线程天然共享进程内存。

控制方式的异同

进程与线程的身份标识ID管理方式不一样,进程的ID为pid_t也就是一个Int型;而线程是一个long型。

在全系统中进程ID是唯一标识,对于进程的管理都是通过PID实现的,每创建一个进程,内核就会创建一个结构体来存储进程的全部信息。每一个存储进程的信息节点也保存着自己的PID,需要管理该进程时就通过这个ID来实现(比如 发送信号)。当进程结束要回收时,需要通过wait()系统调用来进行,僵死进程(defunct):子进程比父进程先结束,而父进程没有调用wait获取子进程的退出码,子进程就变为僵死进程。其进程实体不存在了,但会虚占PID资源。

孤儿进程:父进程比子进程先结束,系统为子进程在重新分配一个父进程(早期孤儿进程会被init进程(pid=1)接受,现在会被随机接受)。

线程ID一般在本进程空间作用,当然系统管理线程时也需要记录其信息,其方式是,在内核创建一个 内核态线程与之对应,这种对应关系是一对多,也就是一个内核线程可以对应多个用户级线程。主线程调用pthread_join()来回收线程(前提是未被detached)。若要主动终止要调用pthread_exit()。

上下文切换开销问题

首先我们要知道什么是上下文切换:

上下文切换是指操作系统在不同的执行单元(如进程、线程)之间切换时,保存当前执行单元的状态(上下文),并恢复下一个执行单元状态的过程。这个过程由操作系统内核(Kernel)完成,是实现多任务并发执行的基础。

上下文包含哪些内容?

  • CPU 寄存器状态:包括程序计数器(PC,指向下一条指令)、通用寄存器(如 x86 的 RAX、RBX)、栈指针(SP)等,这些数据决定了执行单元恢复后从何处继续运行。
  • 内核栈:每个进程 / 线程在内核空间有独立的栈,保存内核函数调用的上下文。
  • 地址空间信息(仅进程切换时涉及):

            - 进程的虚拟地址空间(页表、TLB 缓存等)。

            - 线程共享所属进程的地址空间,因此无需切换地址空间。

为什么进程上下文切换开销更大?

进程切换的高开销源于其资源独立性:每个进程拥有独立的地址空间、文件描述符等资源,切换时必须保证这些资源的完整性和隔离性,导致内核需要执行更多操作(如 TLB 刷新、页表切换、全局资源状态保存)。而线程作为进程内的轻量级执行单元,共享进程资源,切换时仅需处理线程私有状态,因此开销显著更低

  1. 地址空间切换的成本

进程的独立性:每个进程有独立的虚拟地址空间(通过页表映射到物理内存)。
    

  •  切换页表与 TLB 刷新:

当从进程 A 切换到进程 B 时,CPU 需要加载进程 B 的页表(存储在 CPU 的内存管理单元 MMU 中)。由于不同进程的虚拟地址映射不同,必须刷新 TLB(Translation Lookaside Buffer,地址转换缓存),清除之前缓存的地址映射关系。


TLB 刷新的影响:TLB 缓存了虚拟地址到物理地址的映射,若失效,后续内存访问需要多次访问内存查询页表(称为 “页表遍历”),导致 CPU 性能大幅下降。

线程的共享性:同一进程内的线程共享虚拟地址空间,切换时无需更换页表和刷新 TLB,因此省去了这部分开销。
    

    2. 资源保存与恢复的范围

  • 进程切换:需要保存 / 恢复进程级资源,例如:打开的文件描述符、信号处理状态、进程优先级等内核维护的全局状态。
  • 线程切换:仅需保存 / 恢复线程私有资源(如寄存器、栈指针、线程局部存储 TLS 等),而进程级资源(如地址空间、文件句柄)无需变动。

 3. 缓存失效的影响

  •  不同进程的数据和代码可能位于不同的内存区域,切换后 CPU 缓存(L1/L2/L3 Cache)中原本为进程 A 缓存的数据,对进程 B 可能完全无效,导致后续指令和数据访问需要从内存重新加载,增加延迟。
  • 线程切换的缓存友好性:同一进程内的线程共享内存空间,切换后缓存数据通常仍有效,因此缓存命中率更高。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值