通过之前的学习,我们可以了解到进程是正在执行的程序,但是进行在一个时间只能干一件事。如果向同时做两件事,进程是没有办法做到的,但是线程可以做到。
线程就是为了让进程能够同时干多件事而发明的一种“分身术”。
由于线程是进程的分身,那么线程和进程在本质上是一样的,拥有同样的程序文本。但是线程不拥有系统资源,只有运行必须的一些数据结构。但是尽管线程是进程的分身,它们之间还是由区分的,就是线程执行时的上下文不一致。一般来说,我们说:
线程是进程里面的一个执行上下文或者执行序列。执行序列就是一组有序指令的集合——函数。
在线程模式下,一个进程至少有一个线程(主线程,即main方法代表的执行序列),但也可以有多个线程。也就是说,一个进程可以同时拥有多个执行序列。
进程、线程就好像一场舞台剧,舞台上可以有多个演员同时出场,这些演员和舞台就构成了一场剧。其中,每个演员就是一个线程,舞台是地址空间,这个同一个地址空间里面的所有线程就构成了进程。
将进程分解为线程可以有效利用多处理器和多核计算机。在没有线程的情况下,增加处理器并不能提高进程的执行效率。但如果分解为多个线程,则可以让不同的线程同时运转在不同的处理器上,提高进程的执行速度。
线程管理就是维护线程的各种信息,包括线程的各种关键资料。
存放线程信息的数据结构称为线程控制表或线程控制块(TCB)。
基于线程共享一个进程空间,线程的许多资源都是共享的。
共享的资源存放在进程控制块(PCB)里面。不被共享的资源放在线程控制块里面。
资源共享和独享就像在一个教室上课的学生,教室里面很多东西都是共享的,如投影,粉笔,话筒等;但有的东西则是每个人独享的,如座位等。
那么如何判断那些资源为同一进程的不同线程共享,那些资源不被共享?一般的判断标准为:
如果某资源不独享会导致线程运行错误,则该资源就由每个线程独享;而其他资源都由进程里面的所有线程所共享。
基于此,给出一般情况下,同一进程的线程间共享和独享资源的划分:
线程共享资源 | 线程独享资源 |
---|---|
地址空间 | 程序计数器(每个线程的执行序列不一样) |
全局变量 | 寄存器(线程的上下文) |
打开的文件 | 栈(线程的上下文) |
子进程 | 状态字 |
闹铃 | |
信号及信号服务程序 | |
记账信息 |
由于线程是进程的构成部分,那么线程的管理就有两种方式:①进程自己来管理线程,②操作系统来管理线程。
基于这两种不同的选择就出现了线程实现的两种方式:①内核线程实现(操作系统来管理线程),②用户态线程实现(进程自己来管理线程)。
思考:进程为什么不区分实现方式?
答:进程是在CPU上实现并发,而CPU是由操作系统管理的,因此,进程的实现只能由操作系统内核来进行,而不存在用户态实现的情况。
操作系统管理线程,就要保持维护线程的各种信息,即将线程控制块存放在操作系统内核空间。这样,操作系统内核就同时拥有进程控制块和线程控制块。而根据进程控制块和线程控制块提供的信息,操作系统就可以对线程进行类似进程的管理,比如线程调度、线程的资源分配、各种安全措施的实现。
用户态线程是用户自己做线程的切换,自己管理线程的信息,而操作系统无需知道线程的存在。
用户态在调度线程时,必须写一个执行系统作为调度器,即除了正常执行任务的线程外,还有一个专门负责线程调度的线程。
下面用图来展示一下用户态线程实现:
程序编写困难,各线程相互合作使用CPU,程序中考虑什么时候让出CPU使用权较难
无法达到线程目标(进程级多道编程)
在执行过程中,如果一个线程阻塞,它无法将CPU控制权交出,这样整个进程无法向前推进。OS随即将CPU控制权交给另外一个进程。
线程阻塞解决办法:
①在线程执行任何系统调用之前,先确认该调用是否会发生阻塞,如果会,就禁止调用;否则就放行。(前提条件是该调用在等待一段时间后一定会变为非阻塞调用)
②进程阻塞后,激活受阻进程的其他线程。在一个进程挂起后,OS并不立即切换到别的进程,而是该进程第二次机会,让其继续执行。如果该进程只有一个线程,或者其所有线程都已经阻塞,则控制权再次返回给OS。(修改操作系统进程切换时的指令,让其不是立即切换到别的进程;违背了层次架构原则(本应该执行系统调用操作系统);不安全)
基于以上两中实现方式的优缺点,提出了一种将两者结合起来的混合级线程。
用户态的执行系统负责进程内部线程在非阻塞时的切换。
内核态的操作系统负责阻塞线程的切换。
内核态线程数量较少,而用户态线程数量多。
用户态线程被多路复用到内核态线程上,即每个内核线程可以服务一个或多个用户态线程。
如果一个线程阻塞,则与其同一组的线程皆阻塞,但另外一组线程却可以继续执行。
在分配线程时,将需要执行阻塞操作的线程设定为内核态线程,不会执行阻塞操作的线程设为用户态线程。
线程从用户态到内核态的两种情况:
①在程序运行过程中发生中断或者异常,系统将自动切换到内核态运行中断或异常处理机制。
②程序进行系统调用也将造成用户态进入内核态。
线程在进程级别实现了多道编程,使得一个进程可以同时干好多事,提高了程序运行效率,提高了硬件资源的使用率。
①系统运行的不确定性(单个线程的执行效率,执行准确性均存在不确定性)
②多线程中出现异常很麻烦