进程、线程、协程
进程可以理解为一个动态的程序,进程是操作系统进行资源分配的基本单元。
比如电脑上的QQ就是一个进程,微信也是
线程是操作系统进行调度的基本单位,进程占一个虚拟内存空间,而进程内的线程可以共享进程内的虚拟内存
线程的粒度更小,比如微信可以有多个线程,一个负责拉消息,一个负责发消息,一个负责下载文件
协程可以理解为用户态的线程,和线程的区别
- 内存占用更小,大概有2k,且可以动态扩容。而线程有2m,线程是操作系统管理的实体,包括时间片,cpu,线程栈之类的,它占用的资源就比较大么。而协程不归操作系统管理,因为占用的资源就比较少
- 上下文切换开销小,无需在用户态和内核态之间切来切去。线程的切换就需要保存和回复想线程上下文,需要耗费一定的时间和资源。而协程的保存就只需要保存栈帧之类的东西,因此成本是要比线程低的
- 线程是由操作系统调度的,而协程则是由程序员控制,可以在不同任务之间来回切换,不需要等待操作系统调度
- 线程是面向操作系统的,而协程是面向任务的。线程需要使用操作系统提供的api进行线程之间的通信和同步。而协程则可以使用语言级别的协程库进行协作式多任务
进程是怎么切换的
进程切换的步骤有几个。
第一个步骤是中断处理。也就是说进程切换其实是依赖中断来触发的,而后进入中断处理,开始执行切换的代码。
第二个步骤是保存当前进程的上下文。
第三个步骤是根据进程调度策略,选出下一个进程,这里叫做新进程。
第四个步骤是标记新老进程的状态。
第五个步骤是切换虚拟地址空间。
第六个步骤恢复新进程的上下文空间。
最后则是跳转到新进程的代码,开始执行。
简述
中断处理,保存上下文,选择新进程,切换与恢复
引导
进程上下文;虚拟地址空间切换;
从进程切换的这些步骤也能看出来,它的性能是比较差。
而作为对比,线程切换虽然看上去步骤也是类似的,但是实际上就要轻量很多。首先,线程切换不涉及虚拟地址空间切换,那么因为虚拟地址空间切换带来的各种缓存失效等问题都没有;其次,线程之间是共享很多资源的,如文件句柄等,这些共享资源在线程切换的时候都不需要处理。
总之,线程切换比进程切换快,主要是因为线程共享地址空间和资源,减少了地址空间切换和资源管理的开销。这在多线程编程中提供了更高的并发性和性能
为什么虚拟内存地址切换这么慢
进程都有自己的虚拟地址空间,因此虚拟地址空间切换一般就意味着进程切换了。
整个切换过程之所以慢,有很多因素。
第一个是触发用户态和内核态切换。一般来说,虚拟地址空间切换,都会涉及到用户态和内核态的切换的,因此虚拟地址空间是被内核管理的;
第二个是要重新加载 TLB。新的虚拟地址空间导致 TLB 缓存失效,所以需要重新加载新的页表项到 TLB 中;
第三个是页表切换,主要是更新 CPU 中页表基址寄存器,指向新的页表;
第四个是有可能触发 IO 操作,因为新的虚拟地址空间的内容可能在交换区上,需要重新加载进来内存中。
这几个步骤叠加就导致了虚拟地址空间切换是一个比较慢的过程。那么相比之下,同一个进程内的线程都是共享虚拟地址空间的,所以就不会触发虚拟地址空间切换,因此线程切换效率高很多。
进程状态
进程一共有 5 种状态,分别是新建、就绪、运行、阻塞和终止。
其中运行状态就是进程正在 CPU 上运行。
就绪则是说进程已处于准备运行的状态,万事俱备,只欠 CPU 了。
而阻塞状态就是进程正在等待某一事件而暂停运行,比如等待某资源为可用或等待 I/O 完成。即使CPU 空闲,该进程也不能运行。
在这五种状态中有四个比较关键的转换。
第一个是就绪态到运行态,这一般是因为进程被调度了,获得了 CPU 时间片;
第二个是运行态到就绪态,这一般是因为 CPU 时间片耗尽了;
第三个是运行态到阻塞态,这一般是因为进程执行了一些会引起阻塞的操作,最典型的就是 IO 操作;
第四个是阻塞态到就绪态,这一般是因为达成了唤醒条件,例如说 IO 操作完成了;
除了这五种状态以外,还有一种很特殊的状态,也就是所谓的僵尸进程。
当一个进程已经执行完毕并退出时,它本应该被操作系统完全清理掉,但如果没有正确地被清理,它就会变成僵尸进程。例如说在子进程终止后,但其父进程没有正确地调用wait()或waitpid()系统调用来获取子进程的终止状态。这样一来,子进程虽然已经停止运行,但其在进程表中的条目仍然存在。
简单来说,可以认为僵尸进程只会占一条进程表中的条目,但是如果僵尸进程太多了,导致这个进程表满了,那么操作系统就无法继续创建新的进程了。
进程同步
进程之间的同步方式有很多。
第一种是共享内存。也就是多个进程共享同一块内存区域,实现高效的数据交换,但是一般要配合别的同步机制来保护共享内存,避免出现并发问题;
第二种是管道。管道可以认为是一种数据结构,可以在进程之间传递数据,多用于父子进程之间;
第三种是信号量,它可以控制多个进程对共享资源的访问数量,用来实现互斥和同步,允许多个进程按照一定的顺序访问资源;
第四种是信号,用于通知进程某个事件的发生,是一种轻量级的异步通知机制;
第五种是文件锁,即通过锁定文件或文件的某一部分来实现进程间的同步;
那当然,类似锁、原子操作的也可以用来同步。