在现代操做系统中,进程支持多线程。html
进程是资源管理的最小单元;git
线程是程序执行的最小单元。算法
即线程做为调度和分配的基本单位,进程做为资源分配的基本单位数据库
一个进程的组成实体能够分为两大部分:线程集和资源集。进程中的线程是动态的对象;表明了进程指令的执行。资源,包括地址空间、打开的文件、用户信息等等,由进程内的线程共享。服务器
现实中有不少须要并发处理的任务,如数据库的服务器端、网络服务器、大容量计算等。网络
传统的UNIX进程是单线程的,单线程意味着程序必须是顺序执行,不能并发;既在一个时刻只能运行在一个处理器上,所以不能充分利用多处理器框架的计算机。多线程
若是采用多进程的方法,则有以下问题:并发
多线程的优势和缺点其实是对立统一的。框架
支持多线程的程序(进程)能够取得真正的并行(parallelism),且因为共享进程的代码和全局数据,故线程间的通讯是方便的。它的缺点也是因为线程共享进程的地址空间,所以可能会致使竞争,所以对某一块有多个线程要访问的数据须要一些同步技术。异步
在操做系统设计上,从进程演化出线程,最主要的目的就是更好的支持SMP以及减少(进程/线程)上下文切换开销。
不管按照怎样的分法,一个进程至少须要一个线程做为它的指令执行体,进程管理着资源(好比cpu、内存、文件等等),而将线程分配到某个cpu上执 行。
一个进程固然能够拥有多个线程,此时,若是进程运行在SMP机器上,它就能够同时使用多个cpu来执行各个线程,达到最大程度的并行,以提升效率;同 时,即便是在单cpu的机器上,采用多线程模型来设计程序,正如当年采用多进程模型代替单进程模型同样,使设计更简洁、功能更完备,程序的执行效率也更 高,例如采用多个线程响应多个输入,而此时多线程模型所实现的功能实际上也能够用多进程模型来实现,而与后者相比,线程的上下文切换开销就比进程要小多 了,从语义上来讲,同时响应多个输入这样的功能,实际上就是共享了除cpu之外的全部资源的。
针对线程模型的两大意义,分别开发出了核心级线程和用户级线程两种线程模型,分类的标准主要是线程的调度者在核内仍是在核外。前者更利于并发使用多处理器的资源,然后者则更多考虑的是上下文切换开销。
关于线程的实现模型,能够参见博主的另一篇博客线程的3种实现方式–内核级线程, 用户级线程和混合型线程
在目前的商用系统中,一般都将二者结合起来使用,既提供核心线程以知足smp系统的须要,也支持用线 程库的方式在用户态实现另外一套线程机制,此时一个核心线程同时成为多个用户态线程的调度者。
正如不少技术同样,”混合”一般都能带来更高的效率,但同时也 带来更大的实现难度,出于”简单”的设计思路,Linux从一开始就没有实现混合模型的计划,但它在实现上采用了另外一种思路的”混合”。
在线程机制的具体实现上,能够在操做系统内核上实现线程,也能够在核外实现,后者显然要求核内至少实现了进程,而前者则通常要求在核内同时也支持进 程。核心级线程模型显然要求前者的支持,而用户级线程模型则不必定基于后者实现。这种差别,正如前所述,是两种分类方式的标准不一样带来的。
当核内既支持进程也支持线程时,就能够实现线程-进程的”多对多”模型,即一个进程的某个线程由核内调度,而同时它也能够做为用户级线程池的调度 者,选择合适的用户级线程在其空间中运行。这就是前面提到的”混合”线程模型,既可知足多处理机系统的须要,也能够最大限度的减少调度开销。
绝大多数商业 操做系统(如Digital Unix、Solaris、Irix)都采用的这种可以彻底实现POSIX1003.1c标准的线程模型。在核外实现的线程又能够分为”一对一”、”多对 一”两种模型,前者用一个核心进程(也许是轻量进程)对应一个线程,将线程调度等同于进程调度,交给核心完成,然后者则彻底在核外实现多线程,调度也在用 户态完成。后者就是前面提到的单纯的用户级线程模型的实现方式,显然,这种核外的线程调度器实际上只须要完成线程运行栈的切换,调度开销很是小,但同时因 为核心信号(不管是同步的仍是异步的)都是以进程为单位的,于是没法定位到线程,因此这种实现方式不能用于多处理器系统,而这个需求正变得愈来愈大,因 此,在现实中,纯用户级线程的实现,除算法研究目的之外,几乎已经消失了。
Linux内核只提供了轻量进程的支持,限制了更高效的线程模型的实现,但Linux着重优化了进程的调度开销,必定程度上也弥补了这一缺陷。目前 最流行的线程机制LinuxThreads所采用的就是线程-进程”一对一”模型,调度交给核心,而在用户级实现一个包括信号处理在内的线程管理机制。
内核线程就是内核的分身,一个分身能够处理一件特定事情。这在处理异步事件如异步IO时特别有用。内核线程的使用是廉价的,惟一使用的资源就是内核栈和上下文切换时保存寄存器的空间。支持多线程的内核叫作多线程内核(Multi-Threads kernel )。
内核线程只运行在内核态,不受用户态上下文的拖累。
处理器竞争:能够在全系统范围内竞争处理器资源;
使用资源:惟一使用的资源是内核栈和上下文切换时保持寄存器的空间
调度:调度的开销可能和进程自身差很少昂贵
同步效率:资源的同步和数据共享比整个进程的数据同步和共享要低一些。
轻量级进程(LWP)是创建在内核之上并由内核支持的用户线程,它是内核线程的高度抽象,每个轻量级进程都与一个特定的内核线程关联。内核线程只能由内核管理并像普通进程同样被调度。
a LWP runs in user space on top of a single kernel thread and shares its address space and system resources with other LWPs within the same process
轻量级进程由clone()系统调用建立,参数是CLONE_VM,即与父进程是共享进程地址空间和系统资源。
与普通进程区别:LWP只有一个最小的执行上下文和调度程序所需的统计信息。
处理器竞争:因与特定内核线程关联,所以能够在全系统范围内竞争处理器资源
使用资源:与父进程共享进程地址空间
调度:像普通进程同样调度
轻量级线程(LWP)是一种由内核支持的用户线程。它是基于内核线程的高级抽象,所以只有先支持内核线程,才能有LWP。每个进程有一个或多个LWPs,每一个LWP由一个内核线程支持。这种模型实际上就是恐龙书上所提到的一对一线程模型。在这种实现的操做系统中,LWP就是用户线程。
因为每一个LWP都与一个特定的内核线程关联,所以每一个LWP都是一个独立的线程调度单元。即便有一个LWP在系统调用中阻塞,也不会影响整个进程的执行。
轻量级进程具备局限性。
首先,大多数LWP的操做,如创建、析构以及同步,都须要进行系统调用。系统调用的代价相对较高:须要在user mode和kernel mode中切换。
其次,每一个LWP都须要有一个内核线程支持,所以LWP要消耗内核资源(内核线程的栈空间)。所以一个系统不能支持大量的LWP。
注:
- LWP的术语是借自于SVR4/MP和Solaris 2.x。
- 有些系统将LWP称为虚拟处理器。
- 将之称为轻量级进程的缘由多是:在内核线程的支持下,LWP是独立的调度单元,就像普通的进程同样。因此LWP的最大特色仍是每一个LWP都有一个内核线程支持。
用户线程是彻底创建在用户空间的线程库,用户线程的建立、调度、同步和销毁全又库函数在用户空间完成,不须要内核的帮助。所以这种线程是极其低消耗和高效的。
处理器竞争:单纯的用户线程是创建在用户空间,其对内核是透明的,所以其所属进程单独参与处理器的竞争,而进程的全部线程参与竞争该进程的资源。
使用资源:与所属进程共享进程地址空间和系统资源。
调度:由在用户空间实现的线程库,在所属进程内进行调度
LWP虽然本质上属于用户线程,但LWP线程库是创建在内核之上的,LWP的许多操做都要进行系统调用,所以效率不高。而这里的用户线程指的是彻底创建在用户空间的线程库,用户线程的创建,同步,销毁,调度彻底在用户空间完成,不须要内核的帮助。所以这种线程的操做是极其快速的且低消耗的。
上图是最初的一个用户线程模型,从中能够看出,进程中包含线程,用户线程在用户空间中实现,内核并无直接对用户线程进程调度,内核的调度对象和传统进程同样,仍是进程自己,内核并不知道用户线程的存在。
用户线程之间的调度由在用户空间实现的线程库实现。
这种模型对应着恐龙书中提到的多对一线程模型,其缺点是一个用户线程若是阻塞在系统调用中,则整个进程都将会阻塞。
这种模型对应着恐龙书中多对多模型。
用户线程库仍是彻底创建在用户空间中,所以用户线程的操做仍是很廉价,所以能够创建任意多须要的用户线程。
操做系统提供了LWP做为用户线程和内核线程之间的桥梁。LWP仍是和前面提到的同样,具备内核线程支持,是内核的调度单元,而且用户线程的系统调用要经过LWP,所以进程中某个用户线程的阻塞不会影响整个进程的执行。
用户线程库将创建的用户线程关联到LWP上,LWP与用户线程的数量不必定一致。当内核调度到某个LWP上时,此时与该LWP关联的用户线程就被执行。
LinuxThreads是用户空间的线程库,所采用的是线程-进程1对1模型(即一个用户线程对应一个轻量级进程,而一个轻量级进程对应一个特定的内核线程),将线程的调度等同于进程的调度,调度交由内核完成,而线程的建立、同步、销毁由核外线程库完成(LinuxThtreads已绑定到 GLIBC中发行)。
在LinuxThreads中,由专门的一个管理线程处理全部的线程管理工做。当进程第一次调用pthread_create()建立线程时就会先 建立(clone())并启动管理线程。后续进程pthread_create()建立线程时,都是管理线程做为pthread_create()的调用者的子线程,经过调用clone()来建立用户线程,并记录轻量级进程号和线程id的映射关系,所以,用户线程实际上是管理线程的子线程。
LinuxThreads只支持调度范围为PTHREAD_SCOPE_SYSTEM的调度,默认的调度策略是SCHED_OTHER。
用户线程调度策略也可修改为SCHED_FIFO或SCHED_RR方式,这两种方式支持优先级为0-99,而SCHED_OTHER只支持0。
SCHED_OTHER 分时调度策略,
SCHED_FIFO 实时调度策略,先到先服务
SCHED_RR 实时调度策略,时间片轮转
SCHED_OTHER是普通进程的,后两个是实时进程的(通常的进程都是普通进程,系统中出现实时进程的机会不多)。SCHED_FIFO、 SCHED_RR优先级高于全部SCHED_OTHER的进程,因此只要他们可以运行,在他们运行完以前,全部SCHED_OTHER的进程的都没有获得 执行的机会
参考: