进程 、线程
进程作为资源拥有的基本单位,线程作为调度分配的基本单位基本不拥有资源,只拥有一些必不可少的资源,如:程序计数器,局部变量,少数状态参数,返回地址以及堆栈等,这些都是线程私有的,不共享
线程是进程的组成,线程在进程⾥⾯专⻔负责执⾏指令,指令是从进程.text段映射⾥读出来的,然后在进程⾥堆栈上操作⼀些数据,线程处理数据的资源边界是在进程内的。
系统线程和用户线程
用户线程
不需要内核支持而在用户程序中实现的线程,其不依赖于操作系统核心,应用进程利用线程库提供创建、同步、调度和管理线程的函数来控制用户线程。不需要用户态/核心态切换,速度快,操作系统内核不知道多线程的存在,因此一个线程阻塞将使得整个进程(包括它的所有线程)阻塞。由于这里的处理器时间片分配是以进程为基本单位,所以每个线程执行的时间相对减少。
优点
-
线程的调度不需要内核直接参与,控制简单。
-
可以在不支持线程的操作系统中实现。
-
创建和销毁线程、线程切换代价等线程管理的代价比内核线程少得多。
-
允许每个进程定制自己的调度算法,线程管理比较灵活。
-
线程能够利用的表空间和堆栈空间比内核级线程多。
-
同一进程中只能同时有一个线程在运行,如果有一个线程使用了系统调用而阻塞,那么整个进程都会被挂起。另外,页面失效也会产生同样的问题
缺点
- 资源调度按照进程进行,多个处理机下,同一个进程中的线程只能在同一个处理机下分时复用
系统线程
由操作系统内核创建和撤销。内核维护进程及线程的上下文信息以及线程切换。一个内核线程由于I/O操作而阻塞,不会影响其它线程的运行。
优点
- 当有多个处理机时,一个进程的多个线程可以同时执行
缺点
- 由内核进行调度
两者关系
程序执⾏的时候实际上分为两种状态,这个状态会被⼀条线划分,上⾯称之为⽤户态,下⾯称之为系统/内核态。⽤户态执⾏的都是我们⾃⼰写的代码,⽐如我们做的登录、⽤户CPU时间⽚分配⽅式。但是这些都是由操作系统做⽀持的,操作系统⽀持的时候就得进⼊系统态。举个例⼦调⽤⽂件读写操作,实际上是调⽤类似 open 的 API,这个API最终是由操作系统实现的,操作系统实际上会把API翻译成具体的系统调⽤ syscall,然后在操作系统⾥⾯执⾏⼀些代码,所以说这个代码实际上分为⽤户态代码和系统
态代码。当从⽤户态代码进⼊系统态代码调⽤的时候会涉及到上下⽂切换,这是要付出⼀定的代价的。很显然系统线程去创建去调度是要付出这些代价的,所以很多时候系统线程成本会⾮常的⾼,当我们频繁的去创建系统线程销掉系统线程这种代价实在太⼤了。
在这基础上往往会实现这样的模型。在⽤户态抽象很多个执⾏单位,我们把这些⽤户态线程映射到少量的系统线程上⾯去,然后建⽴类似于 Pool 这样的⼀个概念可以复⽤的。内核态的系统线程专⻔负责执
⾏,⽽⽤户态的线程负责存储状态,⽐如说线程栈状态,所有线程执⾏的线程栈是⽤来保存当时执⾏线程状态的,还包含寄存器相关的信息、局部变量,这样的好处是我们把建成Pool以后就不需要频繁的创建系统线程,只需要⽤户态去创建各种各样我们所需的这种抽象的专⻔⽤来存储状态的这种⽤户态线程,我们可以创建很多个,当我们创建好当需要执⾏的时候,把它绑定到⼀个系统线程上⾯去,然后去执⾏执⾏完了以后可以把这个系统线程释放掉,系统线程回到Pool⾥⾯只需要把这个状态杀掉,我们不需要消灭这个系统线程。
因为接下来我们可以把另外⼀个任务重新的调度到这个系统线程上去执⾏。这样⾸先我们创建⼀定数量的系统线程,创建好了这些系统线程专⻔⽤来做执⾏的。第⼆,我们额外在⽤户态空间创建⼀些对象专⻔⽤来保存执⾏时候所需要的状态,其中包括线程栈,它不负责执⾏,因为它只是抽象的⼀个很普通的数据容器,它执⾏的时候把它绑定到某个系统线程上去,这样这个线程就具备了普通线程那种状态然后执⾏,执⾏完了这个线程上的状态全部被剥离掉然后这个线程就变成干净的了原始状态接下来可以执⾏其他的任务。
在现代语⾔当中往往会在系统线程之上做⼀次抽象,就是在⽤户态空间去实现⼤量的专⻔⽤来保存状态的这种⽤户线程,⽤户线程不负责执⾏它只负责保存⽤户状态,所有的执⾏最终交给底层的系统线程执
⾏,所以底层去实现类似像并⾏,⽤户态我们只需要创建像⼤量的并发任务,中间通过调度器来实现这两个层⾯上的绑定从⽽实现把⽤户态这样的执⾏和系统态这样的执⾏分离掉,避免反复的系统调⽤所消耗的资源
网友评论