线程是最小的任务调度单位,是依赖于进程而存在的迷你进程。和进程一样,线程也有三种状态——运行、就绪、阻塞。我认为,线程是进程中任务真正的执行者,而进程提供了内存空间、CPU、程序计数器以及寄存器让线程使用。
为什么要存在
对于进程来说,多个进程之间无法分享内存空间,对于一些应用而言,共享内存空间的能力是必须的,而同一个进程下的多个进程,是共享进程的内存空间的。同时,由于这一特性,线程的创建相较于进程,要快得多。
还有一点,可能也是很多人误解的,那就是线程可以加快程序的运行速度。这是一种错误的观点。多线程并不能加快程序执行的速度,而是能充分利用 CPU,从而给我们造成了程序速度变快的假象。例如,在 I/O 处理时,线程阻塞另一个线程开始,而不需要等待 I/O 完成。
这里,我们应该知道,在单 CPU 的情况下,某一时刻只能有一个进程运行,只能有一个线程运行。
用户态与内核态
线程有两种,一个是内核态线程,一个是用户态线程。
当在一个进程中创建一个内核态线程时,这个线程便陷入了内核中,同时占据 CPU、寄存器和程序计数器,并且和进程一样,将线程表存在内核中。内核对这个线程是有感知的,并且内核直接参与线程的调度。
而用户态线程的状态是存储在进程中的。进程中有一个专门的运行时系统,用于调度用户线程。所以,每个进程可以自己实现自己的调度算法,从而可以增加程序的灵活度。例如,在一个线程要做可能会造成阻塞的事情时,会通知运行时系统,由运行时系统来通过进程中的线程表的线程的状态,来决定调度线程。
但是,由于内核不直接参与调度用户线程,那么就有一个问题,进程调度线程需要线程主动让出,这样用户线程的权利是非常大的。如果一个线程做了造成本地阻塞的事情而不通知进程,那么就会使整个进程一直处于阻塞状态,即使进程中其它线程是就绪状态。
这里插一句,好像上面的事情和现在说的比较多的协程很相似。没错,我认为,协程就是用户态线程,它有相当高的主动权,来通过进程的堆栈来调度,可以有效解决并发的问题。
混合实现
可以看到,内核线程和用户线程都有各自的优点和缺点。
内核线程由内核直接调度,在多处理机系统中可以实现同时运行。而且由于是内核调度,线程没有主动权,可以避免线程占据 CPU 太久而导致其他线程无法运行。但是由于内核线程是内核直接调度的,在调度时会陷入内核,这个代价是非常大的。
用户线程由进程中的运行时系统来调度,所有的线程的数据都保存在进程栈中,而不需要陷入内核,不需要上下文切换,也不需要对缓存进行刷新,所以这样的调度时非常快的。同时用户线程由进程实现调度算法,有很强的扩展性。但是由于用户线程的主动权非常大,可能会导致线程阻塞但其它就绪的线程没有机会运行。而且由于线程的数据均保存在进程的内存中,如果线程很多,可能会占用相当多的内存,从而发生一些问题。
既然内核线程和用户线程由各自的优点,那么可以将它们的有点集合起来。例如,可以使用内核线程,并且内核线程和用户线程多路复用,通过内核线程来控制和使用用户线程。
调度程序激活机制
调度程序激活的目标是模拟内核线程的功能,但是为线程提供在用户空间中才能实现的更好的性能以及更强大的灵活性。
进程的运行时系统,将线程分配在处理器上。当内核了解到一个线程阻塞时,它启动运行时系统,以此当做通知,让运行时系统决定如何调度自己的拥有的线程。
在某个用户线程运行的过程中,发生了一个硬件中断,此时 CPU 进入内核态。如果进程中的线程需要这个中断,进程对中断感兴趣,那么进程将被中断的线程挂起并保存在堆栈中,然后选择线程进行调度。如果不感兴趣,则恢复被中断的线程。
本文是作者阅读《现代操作系统》的一些总结与理解,仅此记录下来已被日后翻阅。同时,也分享给各位希望了解这些知识的同道者们。由于作者水平有限,如有错误之处,望不吝赐教,谨表感谢。
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。