3 多线索体系结构综述 |
多线索编程模式有两个级别。最重要的级别是线索接口,其定义编程模式的大多数方面。即,编程者使用线索编写程序。第二级是轻量的进程(lightweight process)(LWP), 其由操作系统必须提供的服务定义。在描述每一级之后,我们解释为什么两级是必须的。
传统的UNIX进程有一个单线索控制。一个线索的控制或更简单的一个线索是程序中执行的一个指令序列。一个线索有一个程序计数器(PC)和一个栈,以保留局部变量和返回地址。一个多线索UNIX进程本身不再是一个线索控制; 而是和一个或多个线索联结。线索独立地执行。一般地,无法预计不同线索的指令如何交织,尽管它们的执行顺序能影响执行的相对速度。一般情况,一个应用进程选择应用到一个问题的线索的数量在进程外是不可见的。线索能作为用于解决问题的执行资源。
线索共享进程指令和它的大部分数据。一个线索对共享数据的改变能由这个进程的其他线索看见。线索也共享一个进程多数操作的系统状态。每个线索可看到同样的打开的文件。例如,如果一个线索打开一个文件,另一个线索能读这个文件。因为线索共享进程的状态,所以有时线索以惊奇的方式相互影响。用线索编程比普通编程要求更仔细和更规律,在线索之间没有系统支援的保护。
每个线索可以作任何系统调用,并以一般方式和其他进程交互作用。一些操作影响一个进程中的全部线索。例如,如果一个线索调用exit(), 那么所有的线索都被毁坏。其他的系统服务有新的解释;例如,一个浮点溢出使应用陷入一个特别的线索,而不是全部程序。
这个结构提供几个同步设施,以允许线索合作存取共享数据。同步设施包括互斥锁、条件变量和信号量。例如,一个想修改一个变量的线索可能阻止以等待一个互斥锁,这个锁由另一个正修改它的线索拥有。为支持不同频率的交互作用和不同程度的并发性,提供了有不同语义的几个同步机制。
传统的UNIX进程有一个单线索控制。一个线索的控制或更简单的一个线索是程序中执行的一个指令序列。一 个线索有一个程序计数器(PC)和一个栈,以保留局部变量和返回地址。一个多线索UNIX进程本身不再是一个 线索控制; 而是和一个或多个线索联结。线索独立地执行。一般地,无法预计不同线索的指令如何交织,尽 管它们的执行顺序能影响执行的相对速度。一般情况,一个应用进程选择应用到一个问题的线索的数量在进 程外是不可见的。线索能作为用于解决问题的执行资源。
线索共享进程指令和它的大部分数据。一个线索对共享数据的改变能由这个进程的其他线索看见。线索也共 享一个进程多数操作的系统状态。每个线索可看到同样的打开的文件。例如,如果一个线索打开一个文件, 另一个线索能读这个文件。因为线索共享进程的状态,所以有时线索以惊奇的方式相互影响。用线索编程比 普通编程要求更仔细和更规律,在线索之间没有系统支援的保护。
每个线索可以作任何系统调用,并以一般方式和其他进程交互作用。一些操作影响一个进程中的全部线索。 例如,如果一个线索调用exit(), 那么所有的线索都被毁坏。其他的系统服务有新的解释;例如,一个浮点 溢出使应用陷入一个特别的线索,而不是全部程序。
这个结构提供几个同步设施,以允许线索合作存取共享数据。同步设施包括互斥锁、条件变量和信号量。例 如,一个想修改一个变量的线索可能阻止以等待一个互斥锁,这个锁由另一个正修改它的线索拥有。为支持 不同频率的交互作用和不同程度的并发性,提供了有不同语义的几个同步机制。
例如,一个想要更新变量的线索可能会阻塞,以等待由另一个正在更新的线索所保持的互斥锁。为了支持不同的交互频率以及不同的并发度,提供了几个带有不同文法的同步机制。
由图1所示,不同进程中的线索可以通过放在共享存储器中的同步变量与其它线索同步,尽管在不同进程中的线索通常是彼此看不见。同步变量也可以放在文件中,并且拥有所创建进程之下的生命周期。例如,可以创建一个包含数据库纪录的文件。每一纪录包含一个互斥锁,以控制对相关纪录的访问。一个进程可以映射文件,其中的线索可以包含一个与某一特定要修改的纪录相联系的锁。当完成修改时,线索可以释放锁和解除映射此文件。一旦得到锁,如果正在映射文件的任何进程中的任何线索试图得到这个锁,那个线索将会阻塞,直到锁被释放。
线索是一个合适的风范,它针对多数程序希望开发并行硬件或者表示并发进程结构。对于那些情况,要求对程序如何映射到并行硬件有更多的控制,为了优化并发执行和同步的开销,定义了第二个接口。
在SunOS 多线索体系结构中,一个UNIX进程主要包含一个地址空间以及共享此空间的一组轻量进程(LWPs)。每一个LWP可以认为是一个可以执行代码或者系统调用的虚拟CPU,每一个LWP分离地由系统调度,执行独立的系统调用,发生独立的缺页错,以及可以在多处理器上并行执行。系统中所有LWP都由系统核心按照它们的调度类和优先级,调度到可以获得的CPU资源。
通过使用LWP来实现线索。实际上,线索是由一个程序的地址空间上的数据结构来表达。一个进程内的LWP执行线索,如图2所示。一个LWP通过定位进程内存的线索状态,来选择要运行的线索(a)。在装载寄存器和假设识别线索后,LWP执行线索指令(b)。如果线索不能继续,或者其它线索应该运行,LWP将线索状态存回内存中(c)。LWP现在可以选择其它的线索运行(d)。
当一个线索需要通过执行核心调用、发生一个缺页错、或者与其它进程中的线索交互来访问一个系统服务时,通过使用执行它的LWP就能完成。需要系统服务的线索保持绑定在执行它的LWP上,直到系统调用完成。如果线索需要与同一进程的其它线索交互时,它能够完成,而不用涉及操作系统。如图2所示,核心不用知道,就可以发生从一个线索到另一个线索的切换。许多象UNIX stdio库的例程,(例如,fopen()和fread()),用UNIX系统调用(open()和read())来实现,为了许多同样的原因,通过使用LWP接口,来实现线索接口。
LWP也可以有一些不直接对线索输出的能力,例如一个特殊的调度类。一个程序员可以利用这些能力,同时通过说明此线索永久绑定到一个LWP上,保留使用所有的线索接口和能力(例如,同步)。
线索是针对应用程序并行性的主要接口。很少有多线索程序直接使用LWP接口,但是有时知道它在哪是很重要的。一些语言定义了不同于线索的并发机制。一个例子是Fortran编译器提供循环级并行。在这种情况,语言库可以实现它自己的使用LWP的并发的概念。多数程序员使用线索接口进行编程,让库负责将线索映射到核心原语上。应该创建多少LWP以运行线索可以留给库来决定,或者由程序员说明。
人们也许想知道为什么必须有两个如此相似的接口。多线索体系结构必须满足许多不同的期望。有些程序有大量的逻辑并行,例如窗口系统用一个输入句柄和一个输出句柄提供每一组件。其它的程序需要将它们的并行计算映射到可获得的实际处理器上。在这两种情况,程序希望很容易地能完全地访问系统服务。
线索由库实现,核心不知道它。因此,线索可以创建、释放、阻塞、激活等,而不用涉及核心。LWP由系统实现。如果一个线索想要从一个文件读数,当LWP阻塞在文件系统代码上以等待I/O完成时,核心需要能切换到其它进程。当I/O中断到达时,核心不得不保留读操作的状态和继续进行。然而,如果核心总是知道每一线索,它会为每一个分配核心数据结构,并且会涉及到线索的上下文切换,尽管多数线索交互涉及同一进程的线索。换句话说,核心支持的并行(LWP)相对于线索是昂贵的。让核心直接支持所有的线索,会引起象窗口系统这样的应用程序明显地缺乏效率。尽管窗口系统可以极好地表示成大量的线索,仅有一些线索需要在同一实例中激活(例如,需要核心资源,而不是虚拟内存)。
有时拥有多于LWP的线索是一个缺点。一个并行矩阵计算在不同的线索中划分矩阵的行。如果每一处理器有一个LWP,但是每一LWP有多个线索,每一处理器将花费在线索间切换的开销。最好是每一LWP有一个线索,在少量的线索上划分行,减少线索切换的数目。通过说明每一线索永久地绑定到它自己的LWP上,程序员可以编写实际上是LWP代码的线索代码,就象锁定存储页以将虚拟内存切换到实存。
永久地绑定到LWP上的混合线索和非绑定线索也适用于有些应用程序。它的一个例子是一些实时应用,他们希望有些线索有系统级的优先级和实时调度,同时其它线索可以参加后台计算。
通过在体系结构中定义这两个接口级别,可以清楚地区分程序员所看到的和核心所提供的。多数程序员使用线索编程,不考虑LWP。当适当地优化程序行为时,程序员能够调节线索和LWP的关系。这会允许程序员构造假定为极度轻量线索的应用程序,同时带有合适的由核心支持的并发度来承担计算。在某种程度,线索编程者可以将应用程序所使用的LWP认为是应用程序所要求的真实的并发度。
图3显示一个风范的所有部分。将线索分配到LWP上或者由线索包控制或者由程序员说明。核心看到LWP,并且在可获得的处理器上调度它们。
进程1是传统的带有一个附加到单一LWP上的单一线索的进程。进程2拥有在单一LWP上多路复用的线索,象典型的合作包,如SunOS 4.0 liblwp。进程3到5描绘了SunOS多线索体系结构的新功能。进程3存在几个线索,它们在更少数的LWP上多路复用。进程4存在永久绑定到LWP上的线索。进程5显示所有的可能性:一组进程在一组LWP上多路复用,同时有的线索绑定到LWP上。另外,进程要求系统将它的一个LWP绑定到一个CPU上。注意绑定和非绑定线索仍可以在同一进程内部以及通常进程之间彼此同步。
Copyright: NPACT |