进程、线程、协程

进程可以理解为一个动态的程序,进程是操作系统进行资源分配的基本单元

比如电脑上的QQ就是一个进程,微信也是

线程是操作系统进行调度的基本单位,进程占一个虚拟内存空间,而进程内的线程可以共享进程内的虚拟内存

线程的粒度更小,比如微信可以有多个线程,一个负责拉消息,一个负责发消息,一个负责下载文件

协程可以理解为用户态的线程,和线程的区别

  • 内存占用更小,大概有2k,且可以动态扩容。而线程有2m,线程是操作系统管理的实体,包括时间片,cpu,线程栈之类的,它占用的资源就比较大么。而协程不归操作系统管理,因为占用的资源就比较少
  • 上下文切换开销小,无需在用户态和内核态之间切来切去。线程的切换就需要保存和回复想线程上下文,需要耗费一定的时间和资源。而协程的保存就只需要保存栈帧之类的东西,因此成本是要比线程低的
  • 线程是由操作系统调度的,而协程则是由程序员控制,可以在不同任务之间来回切换,不需要等待操作系统调度
  • 线程是面向操作系统的,而协程是面向任务的。线程需要使用操作系统提供的api进行线程之间的通信和同步。而协程则可以使用语言级别的协程库进行协作式多任务

进程是怎么切换的

进程切换的步骤有几个。

第一个步骤是中断处理。也就是说进程切换其实是依赖中断来触发的,而后进入中断处理,开始执行切换的代码。

第二个步骤是保存当前进程的上下文。

第三个步骤是根据进程调度策略,选出下一个进程,这里叫做新进程。

第四个步骤是标记新老进程的状态。

第五个步骤是切换虚拟地址空间。

第六个步骤恢复新进程的上下文空间。

最后则是跳转到新进程的代码,开始执行。

简述

中断处理,保存上下文,选择新进程,切换与恢复

引导

进程上下文;虚拟地址空间切换;

从进程切换的这些步骤也能看出来,它的性能是比较差。

而作为对比,线程切换虽然看上去步骤也是类似的,但是实际上就要轻量很多。首先,线程切换不涉及虚拟地址空间切换,那么因为虚拟地址空间切换带来的各种缓存失效等问题都没有;其次,线程之间是共享很多资源的,如文件句柄等,这些共享资源在线程切换的时候都不需要处理。

总之,线程切换比进程切换快,主要是因为线程共享地址空间和资源,减少了地址空间切换和资源管理的开销。这在多线程编程中提供了更高的并发性和性能

为什么虚拟内存地址切换这么慢

进程都有自己的虚拟地址空间,因此虚拟地址空间切换一般就意味着进程切换了。

整个切换过程之所以慢,有很多因素。

第一个是触发用户态和内核态切换。一般来说,虚拟地址空间切换,都会涉及到用户态和内核态的切换的,因此虚拟地址空间是被内核管理的;

第二个是要重新加载 TLB。新的虚拟地址空间导致 TLB 缓存失效,所以需要重新加载新的页表项到 TLB 中;

第三个是页表切换,主要是更新 CPU 中页表基址寄存器,指向新的页表;

第四个是有可能触发 IO 操作,因为新的虚拟地址空间的内容可能在交换区上,需要重新加载进来内存中。

这几个步骤叠加就导致了虚拟地址空间切换是一个比较慢的过程。那么相比之下,同一个进程内的线程都是共享虚拟地址空间的,所以就不会触发虚拟地址空间切换,因此线程切换效率高很多。

进程状态

进程一共有 5 种状态,分别是新建、就绪、运行、阻塞和终止。

其中运行状态就是进程正在 CPU 上运行。

就绪则是说进程已处于准备运行的状态,万事俱备,只欠 CPU 了。

而阻塞状态就是进程正在等待某一事件而暂停运行,比如等待某资源为可用或等待 I/O 完成。即使CPU 空闲,该进程也不能运行。

在这五种状态中有四个比较关键的转换。

第一个是就绪态到运行态,这一般是因为进程被调度了,获得了 CPU 时间片;

第二个是运行态到就绪态,这一般是因为 CPU 时间片耗尽了;

第三个是运行态到阻塞态,这一般是因为进程执行了一些会引起阻塞的操作,最典型的就是 IO 操作;

第四个是阻塞态到就绪态,这一般是因为达成了唤醒条件,例如说 IO 操作完成了;

除了这五种状态以外,还有一种很特殊的状态,也就是所谓的僵尸进程。

当一个进程已经执行完毕并退出时,它本应该被操作系统完全清理掉,但如果没有正确地被清理,它就会变成僵尸进程。例如说在子进程终止后,但其父进程没有正确地调用wait()或waitpid()系统调用来获取子进程的终止状态。这样一来,子进程虽然已经停止运行,但其在进程表中的条目仍然存在。

简单来说,可以认为僵尸进程只会占一条进程表中的条目,但是如果僵尸进程太多了,导致这个进程表满了,那么操作系统就无法继续创建新的进程了。

进程同步

进程之间的同步方式有很多。

第一种是共享内存。也就是多个进程共享同一块内存区域,实现高效的数据交换,但是一般要配合别的同步机制来保护共享内存,避免出现并发问题;

第二种是管道。管道可以认为是一种数据结构,可以在进程之间传递数据,多用于父子进程之间;

第三种是信号量,它可以控制多个进程对共享资源的访问数量,用来实现互斥和同步,允许多个进程按照一定的顺序访问资源;

第四种是信号,用于通知进程某个事件的发生,是一种轻量级的异步通知机制;

第五种是文件锁,即通过锁定文件或文件的某一部分来实现进程间的同步;

那当然,类似锁、原子操作的也可以用来同步。