什么是线程模型
有了进程模型,我们就不用考虑中断,定时器和上下文切换(进程调度的底层实现),只需要考察并行进程,类似的,有了线程模型之后我们就有了这样一个元素:并行实体共享同一个地址空间和所有可用数据的能力。进程模型中的各个程序是独立工作的,而线程模型中的程序是工作于同一个地址空间并共享这个地址空间的数据。从这种工作理念上的不同我们可以感受到,进程模型是为了各个程序之间独立完成各自的工作,而线程模型是为了各个程序之间合作完成同一个工作。
经典的线程模型
进程模型基于两种独立的概念:资源分组管理与执行。这是两个独立的概念,把其中的执行分离出来就是引入了线程这一概念,线程拥有自己的程序计数器,寄存器,堆栈。进程用于把资源集中到一起,而线程则是在CPU上被执行的实体。
前面说过进程是为了分工,线程是为了合作,一个进程里面可能会有多个线程,这些线程可以访问它们所在进程的地址空间的每一个内存地址,也就是说同一个进程中的线程之间是没有保护的,这是必须的,否则它们是无法合作的,线程概念试图实现的是,共享一组资源的多个线程的执行能力,以便这些线程可以为完成某一项任务而共同工作,下面是进程与线程中的内容的比较。
每个进程中的内容 | 每个线程中的内容 |
---|---|
地址空间 | 程序计数器 |
全局变量 | 寄存器 |
打开文件 | 堆栈 |
子进程 | 状态 |
即将发生的报警 | |
信号与信号处理程序 | |
账户信息 |
和进程一样,线程也可以处于运行、阻塞、就绪、停止
POSIX线程
为了实现可移植的线程程序,IEEE标准1003.1中定义了线程的标准,它定义的线程包叫做Pthread。
每一个Pthread线程都含有一个标志符,一组寄存器(程序寄存器,状态寄存器,堆栈指针等等),以及一组存储在结构中的属性(包括堆栈大小,调度参数,以及使用线程需要的其它属性)。下面是几种常见的函数调用
线程调用 | 描述 |
---|---|
Pthread_create | 创建一个新线程 |
Pthread_exit | 结束调用的线程 |
Pthread_join | 等待一个线程退出 |
Pthread_yield | 释放CPU来运行另一个线程 |
Pthread_attr_init | 创建并初始化一个线程的属性结构 |
Pthread_attr_destory | 删除一个线程的属性结构 |
在用户空间中实现线程
在这种方式下,进程自己管理自己的线程,他呈现给内核的是一个单线程的进程模型,内核对这个进程的内部线程一无所知。所有这类实现都有同样的结构,线程在一个运行时系统的顶部运行,这里的运行时系统是一个管理线程的集合,就是上面的那些Pthread函数,意思就是说,一个进程的线程不再需要操作系统来管理调度,而是程序员自己用这些Pthread函数来管理和释放线程,这意味着,程序员可以自己编写线程调度算法,可以在不支持线程的操作系统上实现多线程,由于这些线程调度是在进程本地完成,所以速度要比内核级线程要快得多,同时用户级线程不像内核级线程需要一些固定的表格和堆栈空间,所以它具有很好的扩展性(因为内核级线程的固定表格和内存空间会占用大量内存空间,而用户级的可以设置垃圾线程回收机制,这样能够腾出许多内存空间)。
用户级线程有那么多好处,但是它也有一些缺点,比如由于它呈现给内核的是一个单线程的进程,因此,一旦有一个线程发生阻塞,这个进程中的所有线程都会被阻塞(内核说:我只看到你阻塞了,我才不管你里面是什么东西造成的阻塞,反正现在你就是不能动)。而且一般程序员是希望在那些经常发生阻塞的应用中使用多个线程,但这正如上面所说,阻塞是我们不希望看到的。虽然有办法解决这种阻塞,但是开销太大,得不偿失。
在内核中实现线程
进程中的线程表被移到了内核中,内核直接接管线程,同时内核中也有进程控制块。此时所有线程切换都是要内核来操作,这种开销是相当可观的,但是它也有自己的优点,那就是可以解决阻塞系统调用的问题,也就是进程不会因为它的某一个线程阻塞而造成整个进程阻塞。
混合实现
在这种模式下,内核看到的是一个多线程的进程模型,但实际上,这个进程内部还有许多用户级线程,这些用户级线程可以多路复用这些内核级线程(也就是说,一个进程里面只能有固定数目的内核级线程,但是有更多的用户级线程,这些线程可以轮流使用这种内核态)。
调度程序激活机制
研究人员试图把内核级线程和用户级线程的优点结合起来,在用户级线程模型中,用户级线程运行在一个运行时系统上,一旦发生阻塞系统调用,内核就会阻塞整个进程中的所有线程,这是我们不想看到的,我们希望内核不要那么武断,希望它把发生的问题告诉那个线程所运行的运行时系统,让运行时系统自己去处理,这就是调度程序激活机制的基本原理。
该机制的基本思路是,当内核了解到一个线程被阻塞后,内核通知该线程的运行时系统,并在堆栈中传递有问题的线程的编号和所发生的事件的一个描述,内核通过在一个已知的起始地址启动运行时系统,从而发出通知,这种机制称为上行调用。当运行时系统得到这个通知,会标记该线程为阻塞并取出另一个线程来运行,当阻塞线程可以运行时,内核又一次发出上行调用,通知运行时系统该线程可以运行,运行时系统会自己决定是否立即运行该线程或者将它放到就绪表中稍后运行。
弹出式线程
一个消息的到达导致该系统创建一个新的线程用于处理这个消息,这种线程就是弹出式线程,由于弹出式线程是全新的,每一个弹出式线程开始的时候都是一样的,因此它的创建十分快速,结果就是消息到达到消息处理开始的时间间隔非常短。