进程:是具有独立功能的一次运行过程,是系统进行资源分配和调度的基本单位。Linux创建新进程时会为其指定和一个唯一的号码,即进程号(PID),以此区别不同的进程。进程不是程序(程序:执行特定任务的一串代码),但由程序产生。程序时一系列指令的集合,是静态的概念;而进程是程序的一次执行过程,是动态的概念。程序长期保存;而进程只能暂时存在,动态的产生,变化和消亡。进程与程序并不一一对应,一个程序可以启动多个进程,一个进程可以调用多个程序。是程序的副本,是有生命周期的。
具有的特征:
动态性:进程是程序的一次执行过程,是临时的,有生命期的,是动态产生,动态消亡的;
并发性:任何进程都可以同其他进程一起并发执行;
独立性:进程是系统进行资源分配和调度的一个独立单位;
结构性:进程由程序、数据和进程控制块三部分组成。
Linux中的进程有以下几种基本状态。
就绪状态:进程已获得除CPU以外的运行所需的全部资源。
运行状态:进程占用CPU正在运行。
等待状态:进程正在等待某一事件或某一资源。
除了以上3中基本状态以外,Linux中的进程还有以下状态
挂起状态:正在运行的进程因为某个原因失去CPU而暂时停止运行。
终止状态:进程已经结束。
休眠状态:进程主动暂时停止运行。
僵死状态:僵尸态,结束进程,父进程结束前,子进程不关闭,杀死父进程可以关闭僵死态 的子进程,简单来说就是进程已经停止运行,但是相关控制信息依旧保留。
拓展:僵尸进程
一个进程结束了,但是如果该进程的父进程已经先结束了,那么该进程就不会变成僵尸进程,因为每个进程结束的时候,系统都会扫描当前系统中所运行的所有进程,看有没有哪个进程是刚刚结束的这个进程的子进程,如果是的话,就由Init来接管它,成为它的父进程,子进程退出后init会回收其占用的相关资源。但是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。
父进程退出 子进程没有退出 那么这些子进程就没有父进程来管理了, 就变成僵尸进程。此时子进程已经结束了,父进程没有意识到。
根据进程所处状态,按时间顺序排列成不同的队列。系统按照一定的策略调度就绪队列中的进程。如果需要尽快完成某个进程的运行,可以通过修改进程的优先级改变它在队列中的排列顺序。
启动进程的用户或者超级用户可以修改进程的优先级,但普通用户只能调低优先级,超级用户既可以调低也可以调高优先级。进程优先级的取值为 -20--19 之间的整数,取值越低,优先级越高,默认为0。
进程上述有详解,下面具体介绍线程,协程。
在早期的操作系统中并没有线程的概念,进程是能拥有资源和独立运行的最小单位,也是程序执行的最小单位。任务调度采用的是时间片轮转的抢占式调度方式,而进程是任务调度的最小单位,每个进程有各自独立的一块内存,使得各个进程之间内存地址相互隔离。后来,随着计算机的发展,对CPU的要求越来越高,进程之间的切换开销较大,已经无法满足越来越复杂的程
序的要求了。于是就发明了线程。线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程可以有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间)。一个标准的线程由线程ID、当前指令指针(PC)、寄存器和堆栈组成。而进程由内存空间(代码、数据、进程空间、打开的文件)和一个或多个线程组成。
协程,英文Coroutines,是一种基于线程之上,但又比线程更加轻量级的存在,这种由程序员自己写程序来管理的轻量级线程叫做『用户空间线程』,具有对内核来说不可见的特性。
因为是自主开辟的异步任务,所以很多人也更喜欢叫它们纤程(Fiber),或者绿色线程
(GreenThread)。正如一个进程可以拥有多个线程一样,一个线程也可以拥有多个协程。协程的目的在传统的J2EE系统中都是基于每个请求占用一个线程去完成完整的业务逻辑(包括事务)。所以系统的吞吐能力取决于每个线程的操作耗时。如果遇到很耗时的I/O行为,则整个系统的吞吐立刻下降,因为这个时候线程一直处于阻塞状态,如果线程很多的时候,会存在很多线程处于空闲状态(等待该线程执行完才能执行),造成了资源应用不彻底。
最常见的例子就是JDBC(它是同步阻塞的),这也是为什么很多人都说数据库是瓶颈的原因。这里的耗时其实是让CPU一直在等待I/O返回,说白了线程根本没有利用CPU去做运算,而是处于空转状态。而另外过多的线程,也会带来更多的ContextSwitch开销。对于上述问题,现阶段行业里的比较流行的解决方案之一就是单线程加上异步回调。其代表派是node.js以及Java里的新秀Vert.x。而协程的目的就是当出现长时间的I/O操作时,通过让出目前的协程调度,执行下一个任务的方式,来消除ContextSwitch上的开销。
协程的特点
线程的切换由操作系统负责调度,协程由用户自己进行调度,因此减少了上下文切换,提高了效率。
线程的默认Stack大小是1M,而协程更轻量,接近1K。因此可以在相同的内存中开启更多的协程。
由于在同一个线程上,因此可以避免竞争关系而使用锁。
适用于被阻塞的,且需要大量并发的场景。但不适用于大量计算的多线程,遇到此种情况,更好实用线程去解决。
协程的原理
当出现IO阻塞的时候,由协程的调度器进行调度,通过将数据流立刻yield掉(主动让出),并且记录当前栈上的数据,阻塞完后立刻再通过线程恢复栈,并把阻塞的结果放到这个线程上去跑,这样看上去好像跟写同步
代码没有任何差别,这整个流程可以称为coroutine,而跑在由coroutine负责调度的线程称为Fiber。比如Golang里的 go关键字其实就是负责开启一个Fiber,让func逻辑跑在上面。
由于协程的暂停完全由程序控制,发生在用户态上;而线程的阻塞状态是由操作系统内核来进行切换,发生在内核态上。因此,协程的开销远远小于线程的开销,也就没有了ContextSwitch上的开销。
线程是程序执行的最小单位,而进程是操作系统分配资源的最小单位;
一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;
进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段、数据集、堆等)及一些进程级的资源(如打开文件和信号),某进程内的线程在其它进程不可见;
调度和切换:线程上下文切换比进程上下文切换要快得多。
如何确定一个程序 是多线程 还是 单线程?
pstree
grep -i threads /proc/ 进程的PID/status
prtstat 进程pid号