进程、线程、纤程、协程

2021/5/19 7:30:14

本文主要是介绍进程、线程、纤程、协程,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!

进程

是操作系统分配资源的最小单位。进程拥有代码和打开的文件资源、数据资源、独立的内存空间。

01 进程的状态

  • 新生 new:被创建,未初始化,不能被调度。
  • 就绪 ready:内核对进程完成初始化,交给调度器,加入运行队列,未被调度。
  • 运行 running:调度器选择该进程,在cpu上运行。
  • 阻塞 blocked:需等待外部事件,暂时无法被调度。
  • 终止 terminated:已完成执行,不会再被调度。内核会回收该进程资源。

02 进程的内存布局空间

进程具有独立的虚拟内存空间:

  • 用户栈:临时数据
  • 代码库:被映射的共享代码库
  • 用户堆:进程动态分配的内存
  • 数据与代码段:进程执行之前,操作系统会将其载入虚拟地址空间;数据是全局变量的值,代码段保存的是进程执行所需代码。
  • 内核栈:当进程由于中断或系统调用进入内核后,会使用内核的栈。

03 进程控制块 process control block

用于保存进程的相关状态,如进程标识符,进程状态,虚拟内存状态,打开的文件等。

04 上下文切换

进程将寄存器状态保存在PCB中,然后将下一个进程之前保存的状态写入寄存器,完成切换。切换过程在内核态完成。

LINUX进程操作

01 进程的创建:fork

linux中,进程通过fork接口从已有进程中分裂出来

  • 父子进程的内存、寄存器、程序计数器等状态完全一致。
  • 父子进程完全独立,PID和虚拟空间不同,各自独立执行。
  • 第一个进程是操作系统创建的,其他所有进程都有该进程产生。

写时拷贝对fork的优化:父子进程共享制度的虚拟页,减少拷贝开销,对于出现写操作的的页,会触发写时拷贝。

多线程的进程执行fork后,子进程只包含一个线程,该线程是调用fork()线程的副本。多线程进程执行fork后,很可能引发死锁。
02 进程的执行 exec

03 进程管理

  • 进程树:内核为进程建立了联系,并在此基础上提供监控、回收、信号分发等功能。
    • 根部是init进程,是操作系统创建的第一个进程,之后所有的进程都是由它直接或间接创建
    • kthreadd进程,所有由内核创建和管理的进程都由它创建
    • login进程,要求用户登录,通过验证后创建出bash进程,是和用户交互的中断
  • 进程监控 waitpid:阻塞等待子进程返回,子进程结束后返回其pid,没有子进程会立即返回。除监控功能外还可回收运行结束的子进程,释放资源。
    • 僵尸进程:父进程没有调用wait,但子进程已经结束,其占用的资源也不会完全释放。内核保留其pid和终止时的status。父进程终止时,其僵尸进程会被init通过wait的方式回收。
  • 进程组 gid:进程的集合
  • 会话 sid:进程组的集合
    • 前台进程组
    • 后台进程组

线程

01 为什么要引入线程?

  1. 创建进程的开销大,需要完成创建独立的地址空间、载入数据和代码段、初始化堆等。即使fork也要对父进程的状态进行大量拷贝。
  2. 由于进程占用独立的虚拟地址空间,进程间的数据共享和同步较麻烦,一般只能基于共享虚拟页(粒度较粗)或进程间通信(开销较大)。
  3. 什么是线程:线程是进程内部的可独立执行单元,共享进程的地址空间,但又各自保存运行时上下文。线程是操作系统调度和管理程序的最小单位。

02 多线程的地址空间布局:

  1. 分离的内核栈和用户栈
  2. 共享的其他区域:进程除了栈以外的其他区域由其所有线程共享。

03 内核态线程:由内核创建,受操作系统调度器直接管理。

04 用户态线程:由应用创建,内核不可见,不直接受系统调度器管理。更加轻量级,创建开销更小,功能也更受限,与内核相关的操作需要内核态线程协助完成。

05 多线程模型:操作系统为实现用户态与内核态线程间协作而建立的两类线程间的关系。

  • 多对一(多用户):每次一个用户态线程可进入内核,其他用户态线程阻塞。
  • 一对一:扩展性更好,无需阻塞。对线程总量有限制,以防造成性能影响。
  • 多对多:用户态>内核态,解决阻塞和性能问题,但管理复杂。

06 线程控制块:用于改变线程状态

  • 内核态TCB:存储线程的运行状态,内存映射,标识符等信息。
  • 用户态TCB:主要由线程库决定,存储更多与用户态相关的信息。
    • 线程本地存储TLS

07 线程的基本接口:

  1. 创建:
  2. 退出exit:线程终止并退出,当线程主函数执行结束后会隐式调用exit
  3. 出让资源yield:线程主动暂停,让出当前cpu给其他线程。线程处于预备状态,可能很快就会被调度。
  4. 合并 join:把指定的线程加入到当前线程,即将两个交替执行的线程合并为顺序执行的线程。
  5. 挂起与唤醒:当线程需要等待外部事件时,可以进入阻塞状态,让出计算资源。
    1. 等待固定时间 sleep,进入阻塞状态,到时间后内核将其唤醒至预备状态。
    2. 等待具体事件:内核使线程挂起,其他线程发送信号后,内核协助线程唤醒至预备状态。
    3. 等待固定时间和等待具体事件的结合:只要满足其一线程就会被唤醒。

08 线程的上下文切换:

cpu通过时间片分配算法来循环执行任务,因为时间片很短,cpu要来回切换任务。cpu在切换任务前要保存当前任务状态,用于切换回之后恢复任务状态。

线程的上下文:cpu寄存器和程序计数器的内容。

时间片:cpu分配给各个线程执行的时间。

线程的组成:

  1. 线程id
  2. 程序计数器:记录要执行的下一条指令
  3. 寄存器:保存当前线程工作变量
  4. 堆栈:记录执行历史,其中每一帧保存了一个已经调用但未返回的过程

纤程

是用户态/轻量级的线程,切换和调度不经过操作系统。

上下文切换:合作式多任务处理,通过yield接口主动暂时放弃cpu,允许其他纤程的调度,需多个纤程进行协作完成调度。不涉及内核态用户态的切换,不涉及对上个上下文的保存。他们通过互相主动切换到其他fiber来交出线程的执行权,各个子任务之间的关系非常强。

协程

当线程数量很多时,线程会占用很多内存空间,过多的线程切换会占用大量系统时间。

协程并没有增加线程数量,只是在线程的基础之上通过分时复用的方式运行多个协程。协程的切换在用户态完成,无需用户态到内核态的消耗。

 

 

 

https://blog.csdn.net/alex_xfboy/article/details/90722654

https://zhuanlan.zhihu.com/p/172471249



这篇关于进程、线程、纤程、协程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!


扫一扫关注最新编程教程