首页 操作系统概念第五章 线程

操作系统概念第五章 线程

举报
开通vip

操作系统概念第五章 线程 http://www.tulipsys.com 操作系统概念(第六版) 第五章 线程 更新日期:2004-9-9 第四章介绍了进程模型,进程模型假设一个进程是一个正在执行的程序,是一个单独的控制执行序列。 现在有许多现代操作系统提供了进程包含多个控制执行序列的特性。本章将介绍多线程计算机系统中的一 些概念,包括了对 Pthread API和 Java线程的讨论。我们将会看到许多与多线程程序设计有关的问题,并 且将了解它(多线程程序设计)是如何影响操作系统的设计的。最后,我们将探讨几个现代操作系统是如...

操作系统概念第五章 线程
http://www.tulipsys.com 操作系统概念(第六版) 第五章 线程 更新日期:2004-9-9 第四章介绍了进程模型,进程模型假设一个进程是一个正在执行的程序,是一个单独的控制执行序列。 现在有许多现代操作系统提供了进程包含多个控制执行序列的特性。本章将介绍多线程计算机系统中的一 些概念,包括了对 Pthread API和 Java线程的讨论。我们将会看到许多与多线程程序设计有关的问 快递公司问题件快递公司问题件货款处理关于圆的周长面积重点题型关于解方程组的题及答案关于南海问题 ,并 且将了解它(多线程程序设计)是如何影响操作系统的设计的。最后,我们将探讨几个现代操作系统是如 何在内核级(kernel level)支持线程的。 5.1 综述 线程,有时也被称为轻量级进程(LWP),是一个基本的 CPU执行单元;它包含了一个线程 ID、一个 程序计数器、一个寄存器组和一个堆栈。它与属于同一个进程的其它的线程共享代码段、数据段,以及其 它的操作系统资源(比如:打开的文件和信号)。一个传统的(或者说重量级)进程有一个单独的控制执 行序列。如果一个进程有多个控制执行序列,那么它就能够同时进行多个任务。图 5.1 说明了传统的单线 程进程和多线程进程的区别。 5.1.1 线程的起源 运行在现代桌面 PC 上的一些软件包是多线程的。应用程序通常被实现为一个拥有多个控制执行序列 的独立的进程。一个网页浏览器可能会有一个线程来显示图片或文本,而同时有其它的线程从网络上获取 数据。一个字处理器可能有一个线程显示图像,另一个线程读取用户按键,并且有第三个线程在后台执行 拼写和语法检查。 Figure 5.1 Single- and multithreaded processes. 在某些情况下,一个单独的应用程序可能需要执行几个相似的任务。例如,一个Web服务器接收到对 网页、图片、声音等的客户端请求。流量比较高的服务器(busy web server)可能同时有很多(可能有数 百个)客户端访问它。如果Web服务器作为一个传统的单线程进程运行,那么它同时只能够响应一个客户 端。客户端等待服务的时间可能会非常长。 一种解决 方案 气瓶 现场处置方案 .pdf气瓶 现场处置方案 .doc见习基地管理方案.doc关于群访事件的化解方案建筑工地扬尘治理专项方案下载 是使服务器作为一个单独的接收请求的进程来运行。当服务器接收到一个请求时,它就 http://www.tulipsys.com 创建一个独立的进程来处理这个请求。事实上,这种创建进程的方法在线程广泛应用之前非常普遍。如上 一章所述,进程的创建是重量级的。如果一个新进程要执行的任务与已存在进程的任务相同,那么何必要 耗费所有这些开销呢?通常更高效的方法是创建一个包含了多个线程的进程来服务同样的请求。这种方法 将使 web 服务进程多线程化。(This approach would multithread the web-server process.)服务器将创建一个 独立的线程来监听客户端请求;当一个请求产生时,它就创建另一个线程来服务这个请求,而不是创建一 个进程。 线程在远程过程调用(RPC)系统中也是一个很重要的角色。第四章讲到 RPC建立了一个类似于普通 的函数或过程调用的通信机制,从而允许进程间通信。典型的 RPC服务器是多线程的。当一个服务器接收 到一个消息时,它使用一个独立的线程处理该消息。这允许服务器服务多个并发的请求。 5.1.2 线程的优点 多线程程序设计的优点可以如下四类: 1. 提高了响应速度:多线程交互式应用程序可以允许程序在它的一部分被阻塞或正在执行一个冗长 的操作时持续运行,从而提高了了对用户的响应速度。例如,一个多线程网页浏览器可以在一个 线程下载图片时利用另外一个线程与用户交互。 2. 资源共享:缺省情况下,线程共享它们所属进程的存储器和资源。代码共享的优点在于它允许应 用程序在同样的地址空间内拥有多个不同的活动线程。 3. 经济实惠:为进程创建分配存储器和资源代价高昂。因为线程共享它们所属进程的资源,所以线 程的创建和上下文转换更为划算。创建和维护进程与创建和维护线程的开销孰大孰小难以测量(一 般根据经验判断),但是通常创建和管理进程比创建和管理线程需要更多的时间。在 Solaris 2中, 创建线程的速度比创建进程快 30倍,上下文转换速度快 5倍。 4. 提高了多处理机体系结构的利用率:在多处理机体系结构中,多线程的优点就更加显著了。在这 种系统中,多个线程可以在不同的处理器上并行运行。一个单线程进程只能够在一个 CPU上运行, 而不论有多少 CPU可供使用。在多 CPU机器中,多线程提高了并发性。在单处理机体系结构中, CPU 通常快速的在每个线程之间移动,如此以至于用户感觉到这是在并行运行(这是个假象), 但是实际上同时只有一个线程在运行。 5.1.3 用户线程和内核线程 至此,我们依然以一种普通的认识来对待线程。然而,通常在用户级(用户线程)或内核级(内核线 程)来提供对线程的支持。 l 对用户线程的支持通常处于内核之上,通过一个用户级线程库(thread library)实现。线程库提 供了对线程的创建、调度和管理的支持,这无需来自内核的支持。因为内核并不知道用户级线程 的存在,所有的线程创建和调度工作都在用户空间完成,而且整个过程不受内核的干涉。所以, 用户级线程的创建和管理通常很快;然而,它们也有一些缺点。例如:如果内核是单线程的,那 么任何用户级线程执行一个导致阻塞的系统调用时就会导致整个进程阻塞,即使程序内部的其它 线程可以运行。用户线程库包括了 POSIX Pthreads、Mach C-threads 和 Solaris 2 UI-threads。 l 内核线程由操作系统直接支持:内核在内核空间内实现了线程的创建、调度和管理。因为线程管 理由操作系统完成,所以内核线程的创建和管理要比用户线程慢。然而,由于线程由内核管理, 如果一个线程执行一个导致阻塞的系统调用,那么内核可以调度程序中的其它线程执行。同样, 在多处理机环境中,内核能够在不同的处理器中调度线程。大多数现代操作系统都支持内核线程, 包括:Windows NT、Windows 2000、Solaris 2、BeOS和 Tru64 UNIX (原先的 Digital UNIX)。 我们将在 5.4节讨论 Pthread,把它作为一个用户级线程库的例子。也将讨论 Windows 2000(5.6节) 和 Solaris 2(5.5节),把它们作为支持内核线程的操作系统的例子。我们还将在 5.7节讨论 Linux是如何 支持线程的(虽然 Linux并不完全区分线程和进程)。 Java也提供了对线程的支持。然而,由于 Java线程的创建和管理通过 Java虚拟机(JVM)完成,所 以就不能简单的把它归为用户线程或内核线程。我们将在 5.8节讨论 Java线程。 5.2 多线程模型 有些系统同时支持用户线程和内核线程,由此产生了不同的多线程模型。我们看一下通常的三种线程 http://www.tulipsys.com 实现类型。 5.2.1 多对一模型 多对一模型(many-to-one model)(图 5.2)将多个用户级线程映射到一个内核线程。线程管理在用户 空间完成,所以它的效率比较高。但是如果一个线程调用了导致阻塞的系统调用的话,那么将阻塞整个进 程。而且,因为一次只有一个线程可以访问内核,所以在多处理机环境中多个线程不能够并发执行。Green thread(一个 Solaris 2的线程库)采用了这种模型。另外,用户级线程库在那些采用了多对一模型不支持 内核线程的操作系统上实现。 Figure 5.2 Many-to-one model. 5.2.2 一对一模型 一对一模型(one-to-one model)(图 5.3)将每个用户线程映射到一个内核线程。它允许在一个线程调 用导致阻塞的系统调用的情况下持续运行其它的线程,从而提供了比多对一模型更好的并发性;它也允许 多个线程在多处理机环境中并行执行。这种模型的唯一缺点在于创建一个用户线程就需要创建一个相应的 内核线程。因为创建内核线程的开销会加重应用程序的负担,所以这种模型的大多数实现都要限制系统支 持的线程数量。Windows NT、Windows 2000 和 OS/2 实现了一对一模型。 5.2.3 多对多模型 多对多模型(many-to-many model )(图 5.4)将用户级线程多路复用到与之数量相等或少一点的内核 线程。(The many-to-many model multiplexes many user-level threads to a smaller or equal number of kernel threads.)内核线程的数量由具体的应用程序或具体的机器确定(分配给应用程序的内核线程数量在多处理 机环境中可能比单处理机环境中多)。然而,多对一模型允许开发者随心所欲的创建用户线程。但是,因 为内核一次只能调度一个线程,所以并不能获得真正的并行性。一对一模型允许更大的并行性,但是开发 者必须小心,以免在一个程序中创建过多的线程(而且在某些情况下可能会限制开发者能够创建的线程的 http://www.tulipsys.com 数目)。多对多模型则免却了所有这些缺点:开发者能够创建所需的用户线程,而且相应的内核线程能够 在多处理机环境中并行运行。而且当一个线程执行导致阻塞的系统调用时,内核能够调度其它的线程执行。 Solaris 2、IRIX、HP-UX和 Tru64 UNIX支持这种模型。 Figure 5.3 One-to-one model. http://www.tulipsys.com Figure 5.4 Many-to-many model. 5.3 与线程相关的问题 在这一节我们讨论一些与多线程程序相关的问题。 5.3.1 fork 和 exec系统调用 在第四章我们描述了怎样使用 fork系统调用创建一个独立的复制的进程。在一个多线程程序中,fork 和 exec 系统调用的语义有所改变。如果一个程序中的线程调用 fork 系统调用,那么新进程是复制所有的 线程呢还是新进程是单线程的呢?有些 UNIX系统拥有两种 fork,一种复制所有的线程,另一种仅仅复制 调用了 fork 系统调用的那个线程。exec 系统调用与第四章所描述的工作方式相同。就是说,如果一个线程 调用了 exec 系统调用,那么传给 exec 的参数中指定的程序将取代整个进程——包括所有的线程和 LWP。 两种 fork 方案的使用要根据应用程序的情况。如果在调用 fork 之后立即调用 exec,那么就无需复制 所有的线程,因为在传给 exec 的参数中指定的程序将取代该进程。在这种情况下,只需复制调用 fork 的 线程就可以了。然而,如果在调用 fork 后并不调用 exec,那就要复制所有的线程。 5.3.2 取消线程 取消线程是指在线程完成之前终止它。例如,如果多个线程并行搜索一个数据库,而一个线程返回了 结果,那么就可能需要取消仍在搜索的线程。当用户点击网页浏览器上的按钮来停止下载一个网页时,也 会发生这种情况(取消线程)。通常是有一个独立的线程下载网页。当用户点击停止按钮时,下载网页的 线程就被取消。 即将被取消的线程通常被称为目标线程(target thread)。目标线程的取消有两种不同的情况: 1. 异步取消(asynchronous cancellation):一个线程立即终止目标线程。 http://www.tulipsys.com 2. 延迟取消(deferred cancellation):目标线程定时 检测 工程第三方检测合同工程防雷检测合同植筋拉拔检测方案传感器技术课后答案检测机构通用要求培训 是否需要终止,这种方式允许目标线程以有 序的方式选择机会终止自身。(The target thread can periodically check if it should terminate, allowing the target thread an opportunity to terminate itself in an orderly fashion.) 如果被取消的线程被分配有资源或一个线程被取消时正在更新与其它线程共享的数据,那么在这种情 况下取消线程会面临一些问题。这对异步取消来说尤其麻烦。操作系统通常从一个被取消的线程中收回系 统资源,但是往往不回收所有的资源。所以,异步取消线程可能不会释放必须的系统资源(a necessary system-wide resource)。 换句话说,延迟取消线程要由一个线程指明一个要被取消的目标线程。然而,只有当目标线程检查决 定自己应该被取消时才会取消。这允许当一个线程能够被安全取消时,该线程检查在某一点是否应该被取 消。Pthread称这样的点为取消点(cancellation point)。 大多数操作系统允许异步取消进程或线程。而 Pthread API提供了延迟取消。这意味着实现了 Pthread API的操作系统允许延迟取消。 5.3.3 信号处理 UNIX 系统使用信号 通知 关于发布提成方案的通知关于xx通知关于成立公司筹建组的通知关于红头文件的使用公开通知关于计发全勤奖的通知 进程发生了某个特定的事件。基于信号的来源和发出信号的原因,信号的接 收可以是同步的或异步的。不论信号是同步的还是异步的,所有的信号都遵从同样的模式: 1. 特定事件的发生产生一个信号。 2. 产生的信号被传送给进程。 3. 信号传送完毕后要得到处理。 同步信号的例子包括非法内存访问和除以零错误。在这种情况下,如果一个正在运行的程序执行了这 样的任意一个操作,就产生一个信号。同步信号被传送给执行了产生该信号的操作的进程(它们因此被认 为是同步的)。 当一个信号在运行的程序外部产生时,进程就要异步接收信号。这种信号的例子有通过指定的按键(如 )来终止一个进程或利用计时器期限。异步信号通常被发送给另外一个进程。 每个信号可能会由如下的某个可能的处理程序处理: 1. 一个缺省的信号处理程序。 2. 一个用户定义的信号处理程序。 在处理信号的时候,每个信号都有由操作系统运行的一个缺省的信号处理程序。这个缺省的行为可能 为用户定义的信号处理程序所重载(override)。对同步和异步信号的处理可能会有所不同。有些信号只是 被忽视了(如改变一个窗口的形状);其它的信号可能会终止一个程序(如非法内存访问)。 在一个单线程程序中处理信号是非常容易的;信号总是传送给一个进程。然而,在多线程程序中传送 信号就复杂多了,因为一个进程可能会有多个线程。那么把信号传送给哪个线程呢? 通常会有如下的四种方式: 1. 将信号传送给信号请求的线程。 2. 将信号传送给进程中的每一个线程。 3. 将信号传送给进程中特定的线程。 4. 分派一个指定的线程接收发给进程的所有信号。 传送信号的方法基于信号产生的类型。例如,同步信号需要被发送给产生信号的线程,而不是进程中 的其它线程。然而,异步信号的情况就不这么明朗了。如终止一个进程(比如)等一些异步 信号应该被发送给所有的线程。有些 UNIX的多线程版本允许一个线程指明它将接收哪些信号阻塞哪些信 号。所以,有些异步信号只能传送给那些不阻塞这些信号的线程。然而有些异步信号只需要处理一次,一 个信号通常只传送给从进程中找到的第一个不阻塞该信号的线程。Solaris 2实现了第四种方式:它在每个 进程中单独创建一个特殊的线程来处理信号。当一个异步信号发送给进程时,它被传送给这个特殊的线程, 然后传送给第一个不阻塞该信号的线程。 Windows 2000 不直接支持信号机制,而是使用异步过程调用(APC)。(Although Windows 2000 does not explicitly provide support for signals, they can be emulated using asynchronous procedure calls (APCs).)APC允 许一个用户线程指定一个函数,该函数在这个用户线程接收到一个特定事件的通知时被调用。因为通过名 http://www.tulipsys.com 称来识别,所以 APC与 UNIX 中的异步信号很相似。然而在多线程环境下 UNIX 必须要解决怎样处理信 号的问题,而 APC是传送给一个特定的线程而不是进程,也更加简单。 5.3.4 线程池 我们在 5.1 节讨论了多线程网页服务器的情况。在这种情况下,不论服务器何时接收到一个请求,它 都会创建一个独立的线程来处理这个请求。而创建一个独立的线程明显要优于创建一个独立的进程,虽然 如此,多线程服务器依然面临一些潜在的问题。第一个问题是在处理请求之前创建线程的时间需求,还要 考虑到该线程一旦完成工作就要被丢弃。第二个问题就更加严重了:如果我们允许为每一个请求都创建一 个新线程来处理,那么我们难以在系统中实现足够数量的线程。不受限制的创建线程可能耗尽系统资源, 比如:CPU时间或存储器。一种解决方案是线程池。 线程池的思想是在进程开始时创建一定数量的线程并将它们置入一个池(pool)中,线程在这个池中 等待工作。当服务器接收到一个请求时,它就从池中唤醒一个线程(如果有可用的线程),由它来处理请 求。一旦线程服务完毕,它就返回线程池等待后面的工作。如果池中没有可用的线程,那么服务器就等待, 直到某个线程被释放。 线程池有如下优点: 1. 利用已存在的线程服务请求要比等待创建一个线程要快。 2. 线程池限制了线程的数量。(A thread pool limits the number of threads that exist at any one point.)在 不能够支持大量的并发线程的系统中这一点特别重要。 线程池中的线程数量的设定要根据一些因素,比如:系统中的 CPU 数量、物理内存的容量和所期望 的并发的客户端请求的数量。更加完善的线程池体系结构能够根据应用情况动态调节线程池中的线程数 量。这样的体系结构优点更多,系统负载较低时拥有一个较小的线程池,因此所需的存储器更少。 5.3.5 Thread-Specific Data 进程中的线程共享属于进程的数据。这种共享的确是多线程程序设计的优点之一。然而,在有些环境 下每个线程可能需要拥有自己的数据。我们把这种数据称为 thread-specific date。例如,在一个事务处理系 统(transaction-processing system)中,我们可能要使用独立的线程处理每个事务。更进一步讲,为事务赋 予一个唯一的标识符。为了使每个线程联系到它的标识符,我们可能要使用 thread-specific date。大多数线 程库(包括 Win32和 Pthread)提供了对 thread-specific date的某些形式的支持。Java也提供了支持。 5.4 Pthread PThread遵照 POSIX 标准 excel标准偏差excel标准偏差函数exl标准差函数国标检验抽样标准表免费下载红头文件格式标准下载 ,该标准规定了线程创建和同步的 API。(Pthread refers to the POSIX standard (IEEE 1003.1c) defining an API for thread creation and synchronization.)这是对线程行为制定的一个规范,而 不是一个实现。操作系统的设计者可以以自己的方式来实现该规范。通常,实现了 Pthread 标准的线程库 被认为是基于 UNIX的系统,如:Solaris 2。虽然在公共领域内可以获取 Windows共享版,但是 Windows 操作系统通常不支持 Pthread。 作为一个用户级线程库的例子,我们将在本节介绍 Pthread API中的部分函数。用 Pthread API创建的 线程和内核线程之间没有任何显著的关系,因此我们说它是一个用户级线程库。(We refer to it as a user-level library because no distinct relationship exists between a thread created using the Pthread API and any associated kernel threads.)图 5.5中的 C 程序示范了创建一个多线程程序的基本的 Pthread API。如果你有兴趣了解 Pthread API更详细的内容,请参考一下本章后的文献注记。 图 5.5中的程序创建了一个独立的线程,该线程计算一个非负整数的和。在 Pthread程序中,独立的线 程在一个指定的函数中开始执行。在图 5.5 中,该函数是 runner。当这个程序开始运行时,一个单独的控 制执行序列在主程序中开始运行。一些初始化工作完成后,主程序创建第二个线程,然后第二个线程运行 runner 函数。 现在我们进一步了解这个程序。所有的 Pthread程序必须要包含 pthread.h头文件。语句 pthread_t tid 声 明了我们将要创建的线程的标识符。每个线程有一组属性,这包括堆栈的大小和调度信息。声明 pthread_attr_t attr 表示了线程的属性。pthread_attr_init (&attr)函数设置属性值。因为这儿并不直接设置任何 属性,所以使用缺省的属性。pthread_create函数创建一个独立的线程。除了要传送线程标识符和线程的属 性值之外,还要传送新线程将开始执行的函数的名称,在此是 runner 函数。最后,我们传送在命令行中提 http://www.tulipsys.com 供的整型参数,argv [1]。 此刻,程序中就有两个线程:一个初始化线程在主函数中运行,一个运行 runner函数求和。(At this point, the program has two threads: the initial thread in main and the thread performing the summation in the runner function.)创建第二个线程之后,主线程通过调用 pthread_join 函数等待 runner 线程执行完毕。在调用 pthread_exit 函数后,runner 线程完成工作。 http://www.tulipsys.com http://www.tulipsys.com Figure 5.5 Multithreaded C program using the Pthread API. 5.5 Solaris 2线程 Solaris 2是一个 UNIX版本,它在内核和用户级、SMP 和实时调度中支持线程。(Solaris 2 is a version of UNIX with support for threads at the kernel and user levels, SMP, and real-time scheduling.)Solar is 2支持 5.4节 讨论的 Pthread API,除此之外还利用一个包含了线程的创建和管理的库来支持用户级线程(被称为 UI线 程)。虽然大多数开发者现在选择 Pthread库,但是这两个库之间的不同点是无关紧要的。Solaris 2也定义 了一个线程的媒介层。在用户级线程和内核级线承之间是轻量级进程(LWP)。每个进程至少包含一个 LWP。 线程库在进程的 LWP池上多路复用用户级线程,只有当前连接到一个 LWP的线程才可以完成工作。(The thread library multiplexes user-level threads on the pool of LWPs for the process, and only user-level threads currently connected to an LWP accomplish work.)其它的用户级线程阻塞或等待 LWP。 标准的内核级线程在内核内部执行所有的操作。每个 LWP 有一个内核级线程,并且有些内核级线程 以内核的名义运行,没有相关的 LWP(例如,服务磁盘请求的线程)。系统中,内核级线程是唯一的调度 对象(第六章)。(Kernel- level threads are the only objects scheduled within the system (Chapter 6).)Solaris 2 实现了多对多模型;图 5.6描述了它的整个线程系统。 用户级线程可能是绑定的或非绑定的。一个绑定的(bound)用户级线程永远附属于一个 LWP。只有 该线程运行在这个 LWP上,而且如果请求的话,这个 LWP可被分配给一个单独的处理器(见图 5.6最右 侧的线程)。(Only that thread runs on the LWP, and by request the LWP can be dedicated to a single processor (see the rightmost thread in Figure 5.6).)绑定线程用于须要快速响应速度的情形,不如实时应用程序。一个 非绑定(unbound)的线程并不总是依附于任何 LWP。一个应用程序中所有的非绑定线程在该应用程序的 LWP池上多路复用。在缺省的情况下线程是非绑定的。Solaris 8也支持一个备用的线程库,该线程库在缺 省的情况下将所有的线程绑定到相关的 LWP。 Figure 5.6 Solaris 2 threads. 考虑一下系统的操作:任何进程都可能有多个用户级线程。这些用户级线程通过线程库调度和在 LWP 间转换,而不需要内核的干涉。用户级线程库的效率非常的高,这是因为线程的创建和销毁不需要内核的 支持,或者是因为线程库从一个用户级线程到另一个进行上下文转换。 每个 LWP仅仅与一个内核级线程连接,然而用户级线程独立于内核。一个进程中可能有多个 LWP, 但是只有当线程要与内核通信时才需要 LWP。例如,在系统调用时可能会同时被阻塞的多个线程可能需要 http://www.tulipsys.com 争用一个 LWP。(For instance, one LWP is needed for every thread that may block concurrently in system calls.) 考虑一下同时有五个不同的文件读请求的情况。它们都要在内核中等待 I/O完成,这就需要五个 LWP。如 果一个任务只有四个 LWP,那么第五个请求就要等待一个 LWP从内核中返回。如果五个 LWP就足以,那 么添加第六个 LWP将一无所获。 内核调度程序调度内核线程,内核线程在系统(单处理机系统或多处理机系统)的 CPU 上执行。如 果一个内核线程阻塞(如等待一个 I/O操作完成),那么处理器就被释放来运行另一个内核线程。如果代表 LWP的线程阻塞,那么 LWP也阻塞。向上,当前附属于该 LWP的用户级线程也阻塞。如果一个进程有多 个 LWP,那么内核可以调度其它的 LWP。 线程库动态的调节池中的 LWP数量以确保应用程序的性能。例如,如果一个进程中的所有 LWP被阻 塞而有其它的线程可以运行,那么线程库就自动的创建其它的 LWP 并将其赋予等待线程。这样,一个程 序就不会因为缺少 LWP 而停止运行。还有,LWP 在不被使用时是一种维护代价高昂的内核资源。(Also, LWPs are expensive kernel resources to maintain if they are not being used.)线程库记录 LWP的“年龄”并在 不再使用的一段时间后删除它们,典型的时间为 5分钟。 在 Solaris 2中,开发者可以使用如下的数据结构实现线程: l 用户级线程包含一个线程 ID;寄存器组(包括一个程序计数器和栈指针);堆栈;优先权(供线 程库调度时使用)。这些数据结构都不是内核资源;全部在用户空间中存在。 l 一个 LWP有一个寄存器组用于它正在运行的用户级线程,也有些内存和记账信息。一个 LWP是 一个内核数据结构,它驻留在内核空间中。 l 一个内核线程只有小的数据结构和一个堆栈。这个数据结构包含一个内核寄存器的拷贝、一个指 向它所附属的 LWP的指针和优先权及调度信息。 Figure 5.7 Solaris 2 process. Solaris 2中的每个进程包含了进程控制块(PCB)中所描述的诸多信息,PCB在 4.1.3节中讨论。特别 的,一个 Solaris 2进程包含了一个进程 ID(PID)、内存映象、打开的文件的列表、优先权信息和指向与 进程关联的内核线程列表的指针(图 5.7)。 5.6 Window 2000线程 Windows 2000 实现了 Win32 API。Win32 API 是 Microsoft 操作系统的基本的( primary)API(Windows 95/98/NT和 Windows 2000。本节提到的很多内容的确都适用于这一操作系统家族。 Windows应用程序作为独立的进程运行,进程中可以包含一个或多个线程。(A Windows application runs as a separate process where each process may contain one or more threads.)Windows 2000 采用在 5.2.2节所描 述的一对一映射方式,每个用户级线程映射到一个关联的内核线程。然而 Windows提供了对 fiber库的支 持,它提供了多对多模型的功能(5.2.3节)。属于一个进程的每个线程都可以访问该进程的虚拟地址空间。 http://www.tulipsys.com 一个线程通常包含如下几个组成部分: l 标识线程的一个唯一的线程 ID。 l 描述处理器状态的寄存器组。 l 一个用户堆栈,当线程在用户摸式下运行时使用。相似的,每个线程也有一个内核堆栈,当线程 在内核模式下运行时使用。 l 一个私有的存储空间,以供各种运行时库和动态链接库(DLL)使用。 寄存器组、堆栈和私有存储空间被称为线程的上下文(context),它们构建在指定的硬件中,而操作 系统运行在这些硬件之上。线程的基本数据结构包括: l ETHREAD(执行线程块)。 l KTHREAD(内核线程块)。 l TEB(线程环境块)。 ETHREAD 的关键组成部分包括一个指向线程所属进程的指针和线程开始控制的函数的地址。 ETHREAD 也包含了指向相应的 KTHREAD 的指针。 KTHREAD包括线程的调度和同步信息。另外,KTHREAD 也包括了内核堆栈(当线程运行在内核摸 式时使用)和一个指向 TEB的指针。 ETHREAD 和 KTHREAD 完全存在于内核空间之中;这意味着只有内核能够访问它们。TEB是一个用 户空间数据结构,当线程在用户摸式下运行时访问它。TEB在其它的段中包含了一个用户模式堆栈和一个 - thread-specific data数组(它被 Windows称为 thread-local storage)。 5.7 Linux线程 Linux内核在 2.2版中引入了线程。Linux提供了一个 fork系统调用,它像传统的 fork功能那样复制一 个进程。Linux也提供了 clone系统调用,它与创建一个线程类似。clone创建一个与调用 clone的进程共享 地址空间的独立的进程,而不像 fork 那样创建一个调用 fork的进程的拷贝,除此之外 clone的行为与 fork 非常相似。通过与父进程共享地址空间,克隆的作业的行为与独立的线程非常相似。 因为在 Linux内核中表示进程,所以就允许共享地址空间。(The sharing of the address space is allowed because of the representation of a process in the Linux kernel.)系统中每个进程都有一个唯一的内核数据结构。 然而,进程的数据并不存储在这个数据结构里面,而是在数据结构中包含一个指向存储这些数据的地址的 指针。例如,每个进程数据结构中包含了指向其它的数据结构的指针,这些数据结构表示打开的文件列表、 信号处理信息和虚拟内存。当调用 fork 时,新进程的创建伴随着父进程的所有相关的数据结构的拷贝。 (When fork is invoked, a new process is created along with a copy of all the associated data struc tures of the parent process.)当调用 clone系统调用时,就创建一个新进程。然而,并不是拷贝所有的数据结构,而是 新进程指向父进程的数据结构,因此就允许子进程共享父进程的内存和其它的进程资源。一组标识(flag) 作为参数被传送给 clone系统调用。这组标识用于指示子进程共享父进程的资源数量。如果没有设定标识, 那么就没有共享,clone 的行为就像 fork 那样。如果设定了所有五个标识,那么子进程就共享父进程的一 切。其它标识组合允许在这两个极端之间的各种共享级别。(Linux提供了五个共享标识:CLONE_VM(共 享内存空间)、CLONE_FS(共享文件系统信息)、CLONE_FILES(共享文件描述符表)、CLONE_SIGHAND (共享信号句柄表)和 CLONE_PID(共享进程 ID,仅对核内进程,即 0号进程有效))。 有趣的是,Linux并不区分进程和线程。事实上,当提及程序控制流时,Linux通常使用术语任务(task), 而不是进程或线程。除了克隆的进程之外,Linux不支持多线程、独立数据结构(separate data structure) 或内核程序(kernel routine)。然而,有多种 Pthread实现了用户级多线程。 5.8 Java线程 如前所述,可以通过在用户级提供一个库(如 Pthread)来实现对线程的支持。而且,大多数操作系统也 在内核级提供对线程的支持。Java是少数在语言级提供了线程的创建和管理的语言之一。然而,因为 Java 虚拟机(JVM)管理线程,不是由用户级库或内核管理,所以就难以把 Java线程归类为用户级或内核级。 这一节我们介绍 Java线程,它既不是严格的用户级模型也不是严格的内核级模型。(In this section we present Java threads as an alternative to the strict user - or kernel-level models.)稍后,我们将讨论 Java线程是如何被 映射到底层内核线程(underlying kernel thread)的。 http://www.tulipsys.com 所有的 Java程序都至少包含一个控制执行序列。即使是仅仅由一个主函数(main method)构成的简 单的 Java程序也要把该主函数作为一个单独的线程在 JVM 中运行。另外,Java提供了允许开发者创建和 维护程序中另外的线程的命令。 5.8.1线程的创建 创建线程的一个方法是创建一个继承自 Thread类的新类,并重载 Thread类的 run方法。图 5.8描述了 这种方法,一个多线程程序的 Java版本计算一个非负整数的和(the summation of a non-negative integer)。 这个继承类的一个对象将作为一个独立的控制执行序列在 JVM 中运行。然而,创建一个继承自 Thread 类的对象并不显明的创建一个新线程;实际上是 start 方法创建了一个新线程。为新对象调用 start 方法要 做两件事: 1. 在 JVM 中为新线程分配内存并初始化该线程。 2. 调用 run方法,使该线程能够在 JVM 中运行。(注意:永远不要直接调用 run方法。而是调用 start 方法,然后由它调用 run方法。) http://www.tulipsys.com Figure 5.8 Java program for the summation of a non-negative integer. 当 Summation 程序运行时,JVM 创建两个线程。第一个是与应用程序关联的线程——该线程在主函 数中开始执行。第二个线程是由 start 方法直接创建的 Summation线程。Summation线程在它的 run方法中 开始执行。当该线程从 run方法中退出时就终止运行。 5.8.2 JVM和主机操作系统 JVM 通常在一个主机操作系统之上实现。这种设置允许 JVM隐藏底层操作系统的实现细节,提供一 致的抽象环境来允许 Java程序在任何支持 JVM 的平台上运行。JVM 规范并不指明怎样把 Java线程映射到 http://www.tulipsys.com 底层操作系统中,而把它留给了具体的 JVM 实现。Windows 95/98/NT 和 Windows 2000采用了一对一模型; 所以,运行在这些系统上的每个 Java线程都要映射到一个内核线程。Solaris 2最初使用多对一模型来实现 JVM(被称为 Green线程)。然而, 为 Solaris 2.6提供的 JVM 1.1采用了多对多模型实现。 5.9 摘要 线程是程序中的一个控制流。一个多线程程序在同样的地址空间内包含了多个不同的控制流。多线程 技术的优点包括:提高了对用户的响应速度、进程内的资源共享、经济实惠和能够充分发挥多处理机体系 结构的优势。 用户级线程对程序员可视,而内核却不知道它的存在。(User-level threads are threads that are visible to the programmer and are unknown to the kernel.)典型的,在用户空间内的线程库管理用户级线程。操作系统 内核支持和管理内核级线程。通常,用户级线程的创建和管理速度比内核线程要快。由三种不同的用户线 程和内核线程关联的方法:多对一模型将多个用户模型映射到一个单一的内核模型。一对一模型将每个用 户线程映射到一个相应的内核线程。多对多模型(many-to-many model )将用户级线程多路复用到与之数 量相等或少一点的内核线程。(The many-to-many model multiplexes many user threads to a smaller or equal number of kernel threads.) 多线程程序的引入使程序员面临一些挑战,包括 fork 和 exec 系统调用的语义。其它的问题涉及到线 程的取消、信号处理和 thread-specific data。一些现代操作系统提供了对线程的内核支持;其中包括了 Windows NT 和 Windows 2000、Solaris 2和 Linux。Pthread API在用户级提供了一系列用于创建和管理线 程的函数。Java提供了一个类似的 API用于支持线程。然而,因为由 JVM 管理 Java线程,而不是由用户 级或内核级线程库管理,所以它们并不属于用户级线程或内核级线程。 词汇 轻量级进程:lightweight process, LWP 多处理机体系结构:multiprocessor architecture 用户线程:user thread 内核线程:kernel thread 用户级:user level 内核级:kernel level 异步过程调用:asynchronous procedure call, APC 线程池:thread pool 运行时库:run-time library 动态链接库(DLL):dynamic link library, DLL 主机操作系统:host operating system 栈指针:stack pointer tulipsys@sohu.com 戈尔巴乔夫开车 一次,苏联领袖戈尔巴乔夫耽心赶不上会议,告诉他的司机开快车。司机因怕违章拒绝了他。戈尔巴 乔夫便命令司机坐在后座位上,亲自开车。 车行不到几英里,就被巡逻队警察拦住,警官派他的警士将违章者拘留起来。 几分钟后,那位警士回来报告说,坐车的人是一位显要人物,不好究办。 “那是谁?”警官询问警士。 “我说不准,警官同志,”警士回答说:“不过戈尔巴乔夫是他的司机。”
本文档为【操作系统概念第五章 线程】,请使用软件OFFICE或WPS软件打开。作品中的文字与图均可以修改和编辑, 图片更改请在作品中右键图片并更换,文字修改请直接点击文字进行修改,也可以新增和删除文档中的内容。
该文档来自用户分享,如有侵权行为请发邮件ishare@vip.sina.com联系网站客服,我们会及时删除。
[版权声明] 本站所有资料为用户分享产生,若发现您的权利被侵害,请联系客服邮件isharekefu@iask.cn,我们尽快处理。
本作品所展示的图片、画像、字体、音乐的版权可能需版权方额外授权,请谨慎使用。
网站提供的党政主题相关内容(国旗、国徽、党徽..)目的在于配合国家政策宣传,仅限个人学习分享使用,禁止用于任何广告和商用目的。
下载需要: 免费 已有0 人下载
最新资料
资料动态
专题动态
is_662875
暂无简介~
格式:pdf
大小:1MB
软件:PDF阅读器
页数:0
分类:互联网
上传时间:2010-11-15
浏览量:10