线程外文翻译资料

 2023-01-20 10:01

线程

  1. 概述

线程是CPU使用的基本单元,它由线程ID、程序计数器、寄存器集合和栈组成。它与属于同一进程的其他线程共享代码段、数据段和其他操作系统资源,如打开文件和信号。一个传统重量级的进程只有单个控制线程。如果进程有多个控制线程,那么它能做多个任务。图说明了传统单线程进程和多线程进程的差别。

1.1动机

运行在现代桌面PC上的许多软件包都是多线程的。一个应用程序通常是作为一个具有多个控制线程的独立进程实现的。例如,网页浏览器可能有一个线程用于显示图形,另一个线程用于读入用户的键盘输入,还有一个线程用于在后台进行拼写和语法检查。

有的时候,一个应用程序可能需要执行多个相似任务。例如,网页服务器接收用户关于网页、图像、声音等的请求。一个忙碌的网页服务器可能有多个(或数千个)客户并发访问它。如果网页服务器作为传统单个线程的进程来执行,那么只能一次处理一个请求。这样,客户必须等待很长的处理请求的时间。

一种解决方法是让服务器作为单个进程运行接受请求。当服务器收到请求是,它会创建另一个进程以处理请求。事实上,这种进程创建方法在线程流行之前很常用。如上一章所述,进程创建很耗时间和资源。如果新进程与现有进程执行同样的任务,那么为什么需要这些开销呢?如果一个具有多个线程的进程能达到同样的目的,那么将更为有效。这种方案要求网页服务器是多线程的。服务器创建一个独立线程以监听客户请求。当有请求时,服务器不是创建进程,而是创建线程以处理请求。

线程在远程过程调用(RPC)系统中也有很重要的作用。回顾第3章,RPC通过提供一种类似于普通函数或子程序调用的通信机制,以允许进程通信。通常,RPC服务器是多线程的。当一个服务器接收到消息,它使用独立线程处理消息。这允许服务器能处理多个并发请求。Java 的RMI系统也以类似方式工作。

最后,现代的许多操作系统都是多线程的,少数线程在核中运行,每个进程完成一个指定的任务,如管理设备或中断处理。例如,Solaris在内核中特别为中断处理创建线程集合。Linux使用内核线程来管理系统中的空闲内存数量。

1.2优点

多线程编程具有如下4个优点:

  1. 响应度高:如果对一个交互程序采用多线程,那么即使其部分阻塞或执行较冗长的操作,该程序仍能继续执行,从而增加了对用户的影响程度。例如,多线程Web浏览器在用一个线程装入图像时,能通过另一个线程与用户交互。
  2. 资源共享:线程默认它们所属进程的内存和资源。代码和数据共享的优点是它能允许一个应用程序在同一地址空间有多个不同的活动线程。
  3. 经济:进程创建所需要的内存和资源的分配比较昂贵。由于线程能共享它们所属进程的资源,所以创建和切换线程会更为经济。实际地测量进程创建和管理与线程创建和管理的差别较为困难,但是前者通常要比后者花费更多的时间。例如,对于Solaris,进程创建要比线程慢30倍,而进程切换要比线程切换慢5倍。
  4. 多处理器体系结构的利用:多线程的优点之一是能充分使用多处理器体系结构,以便每个进程能并行运行在不同的处理器上。不管有多少CPU,单线程进程只能运行在一个CPU上。在多CPU上使用多线程加强了并发功能。
  5. 多线程模型

迄今为止只是泛泛地讨论了线程。不过有两种不同方法来提供线程支持:用户层的用户线程或内核层的内核线程。用户线程受内核支持,而无须内核管理:而内核线程由操作系统直接支持和管理。事实上所有当代操作系统,如Windows XP、Linux、Mac OS、Solaris、Tru64 UNIX,都支持内核线程。

最后,在用户线程和内核线程之间必然存在一种关系。本节研究三种常用的建立此关系的方法。

2.1多对一模型

多对一模型将许多用户级线程映射到一个内核线程。线程管理是由线程库在用户空间进行的,因而效率比较高。但是如果一个线程执行了阻塞系统调用,那么整个进程会阻塞。而且,因为任一时刻只有一个线程能访问内核,多个线程不能并行运行在多处理器上。Green thread(Solaris所应用的线程库)就使用了这种模型,另外还有GNU可一直线程。

2.2一对一模型

一对一模型将每个用户线程映射到一个内核线程。该模型在一个线程执行阻塞系统调用时,能允许另一个线程继续执行,所以它提供了比多对一模型更好的并发功能:它也允许多个线程能并行地运行在多个处理器系统上。这种模型的唯一缺点是每创建一个用户线程就需要创建一个相应的内核线程。由于创建内核线程的开销会影响应用程序的性能,所以这种模型的绝大多数实现限制了系统所支持的线程数量。Linux与Windows操作系统家族实现了一对一模型。

2.3多对多模型

多对多模型多路复用了许多用户线程到同样数量或更小数量的内核线程上。内核线程的数量可能与特定应用程序或特定机器有关(位于多处理器上的程序可比单处理器上分配更多数量的内核线程)。虽然多对一模型允许开发人员创建任意多的用户线程,但是因为内核只能一次调度一个线程,所以并没有增加并发性。一对一模型提供了更大的并发性,但是开发人员必须小心,不要在应用程序内创建太多的线程(有时可能会限制创建线程的数量)。多对多模型没有这两者的缺点:开发人员可创建任意多的用户线程,并且相应内核线程能在多处理器系统上并发执行。而且,当一个线程执行阻塞系统调用时,内核能调度另一个线程来执行。

一个流行的多对多模型的变种仍然多路复用了许多用户线程到同样数量或更小数量的内核线程上,但也允许将一个用户线程绑定到一个内核线程上。这个变种有时被称为二级模型,被IRIX、HP-UX、Tru64 UNIX等操作系统所支持。Solaris在其Solaris9之前的版本中支持二级模型,但从Solaris9开始使用一对一模型。

  1. 线程库

线程库为程序员提供创建和管理线程的API。主要有两种方法来实现线程库。第一种方法实在用户空间中提供一个没有内核支持的库,此库的所有代码和数据结构都存在于用户空间中。调用库的一个函数只是导致了用户空间中的一个本地函数调用,而不是系统调用。第二种方法是执行一个由操作系统直接支持的内核级的库。此时,库的代码和数据结构存在于内核空间中。调用库中的一个API函数通常会导致对内核的系统调用。

目前使用的三种主要的线程库是:(1)POSIX Pthread、(2)Win32、(3)Java。Pthread作为POSIX标准的扩展,可以提供用户级或内核级的库。Win32线程库是适用于Windows操作系统的内核级线程库。Java线程API允许线程在Java程序中直接创建和管理。然而,由于大多数JVM实例运行在宿主操作系统之上,Java线程API通常采用宿主操作系统上的线程库来实现。这意味着在Windows系统上,Java线程通常用Win32 API实现,而在UNIX和Linux系统中采用Pthread。

接下来介绍采用这三种线程库来创建基本的线程。作为一个说明性的例子,设计一个多线程程序,在独立的线程中完成非负整数的加法功能:

例如,如果N为5,此函数表示从0—5的数将加起来,为15。三个程序都要求在命令行输入加法的上限。例如,如果用户输入8,将会输出从0—8的整数值的和。

3.1 Pthread

Pthread是由POSIX标准为线程创建和同步定义的API。这是线程行为的规范,而不是实现。操作系统设计者可以根据意愿采取任何实现形式。许多操作系统实现了这个线程规范,包括Solaris、Linux、Mac OS X和Tru64 UNIX,公开课获取的Shareware实现适用于各种Windows操作系统。

如图所示的C程序显示部分构造一个多线程程序的基本Pthread API,它通过一个独立线程计算非负整数的累加和。对于Pthread程序,独立线程是通过特定函数执行的。在图中,这个特定函数是runner()函数。当程序开始时,单个控制线程在main()中开始。

在初始化之后,main()创建了第二个线程并在runner()中开始控制。两个线程共享全局数据sum。

现在对该程序做一个更为详细的描述。所有Pthread程序都需要包括pthread.h头文件。语句pthread_t tid声明了所创建线程的标示符。每个线程都有一组属性,包括栈大小和调度信息。Pthread_attr_ attr表示线程的属性,通过函数调用pthread_attr_init(amp;attr)来设置这些属性。由于没有明确地设置任何属性,故使用提供的默认属性。(由Pthread API提供的一些属性)。通过函数调用pthread_create()创建一个独立线程。除了传递线程标示符和线程属性外,还要传递函数名称(这里为runner())以便新线程可以开始执行。最后传递由命令参数argv[1]所提供的整数参数。

程序此时有两个线程:main()的初始(父)线程和通过runner()函数执行累加和(子)线程。在创建了累加和线程之后,父线程通过调用pthread_join()函数,以等待runner()线程的完成。累加和线程在调用了函数pthread_exit()之后就完成了。一旦累加和线程返回,父线程将输出累加和的值。

3.2 Win32线程

采用Win32线程库创建线程的技术在某方面类似于Pthread技术。图给出了C程序中的Win32线程。注意,在使用WIn32 API时必须包括windows.h头文件。

正如图所示的Pthread例子,独立线程共享的数据(在此为sum)被声明为全局变量(DWORD数据类型是一个无符号的32位整数),还定义了一个在独立的线程中完成的Summation()函数,向该函数传递一个void指针,Win32将其定义为LPVOID。完成此函数的线程将全局函数sum赋值为从0到传递给Summation()的参数的和。

在Win32 API中,线程的创建还使用了CreateThread()函数(正如在Pthread中那样)将一组线程的属性传递给此函数。这些属性包括包含安全信息、栈大小、一个用以表明挂起状态的线程是否开始的标志。这个线程中采用了这些属性的默认值(没有将属性初始化为挂起状态,而是使其具有被CPU调度的资格)。一旦创建了累加和线程,在输出累加值之前,父线程必须等待累加和线程完成,因为该值是累加和赋予的。回顾Pthread程序其中采用pthread_join()语句实现父线程等待累加和线程。在WIn32中采用同等功能的函数WaitForSingleObject(),从而使创建者线程阻塞,直到累加和线程退出。

3.3 Java线程

线程是Java程序中程序执行的基本模型,Java语言和它的API为创建和管理线程提供了丰富的特征集。所有Java程序至少由一个控制线程组成——即使一个只有main()函数的简单Java程序也是在JVM中作为一个线程运行的。

在Java程序中有两种创建线程的技术。一种方法是创建一个新的类,它从Thread类派生,并重载它的run()函数。另外一种更常见的使用方法是定义一个实现Runnable接口的类。Runnable接口定义如下:

当一个类执行Runnable时,它必须定义run()函数。而实现run()函数的代码被作为一个独立的线程执行。

图是计算非负整数累加和的多线程例子的Java版。Summation类实现了Runnable接口。通过创建一个Thread类的对象实例和传递Runnable对象的结构来创建线程。

创建Thread对象并不会创建一个新的线程,实际上是用start()函数来创建线程。微信的对象调用start()函数需要做两件事:

在JVM中分配内存并初始化新的线程。

调用run()函数,使线程适合在JVM中运行(注意,从不直接调用run()函数,而是调用starr()函数,然后它再调用run()函数)。

当累加和程序运行时,通过JVM创建两个线程。第一个是父线程,它在main()函数中开始运行。第二个线程是在调用Thread对象的start()函数创建,这个子线程在Summation类的run()函数中开始运行。在输出累加和的值后,当此线程从run()函数中退出后线程终止。

在Win32和Pthread中线程间共享数据很方便,因为共享数据被简单地声明为全局数据。作为一个纯面向对象语言,Java没有这样的全局数据的概念。在Java程序中如果两个或更多的线程需要共享数据,通过向相应的线程传递对共享对象的引用来实现。在图所示的Java程序中,main线程和累加和线程共享Sum类的对象实例,通过getSum()和setSum()函数引用共享对象(读者可能好奇为什么不使用java.Iang.Integer对象,而是设计一个新的Sum类。这是因为java.lang.Integer类是不可变的——即一旦被赋予值,就不可改变)。

回忆一下Pthread和Win32库中的父线程,它们在继续之前,分别使用pthread_join或

WaitForSingleObject()等待累加和线程结束。Java中的join()函数提供了类似的功能。注意,join()可能扔掉中断异常,这里选择忽略。

4.多线程问题

本节将讨

剩余内容已隐藏,支付完成后下载完整资料


资料编号:[141608],资料为PDF文档或Word文档,PDF文档可免费转换为Word

您需要先支付 30元 才能查看全部内容!立即支付

课题毕业论文、开题报告、任务书、外文翻译、程序设计、图纸设计等资料可联系客服协助查找。