第五章 线程
更新日期:2004-9-9
第四章介绍了进程模型,进程模型假设一个进程是一个正在执行的程序,是一个单独的控制执行序列。现在有许多现代操作系统提供了进程包含多个控制执行序列的特性。本章将介绍多线程计算机系统中的一些概念,包括了对Pthread API和Java线程的讨论。我们将会看到许多与多线程程序设计有关的问题,并且将了解它(多线程程序设计)是如何影响操作系统的设计的。最后,我们将探讨几个现代操作系统是如何在内核级(kernel level)支持线程的。
5.1 综述
线程,有时也被称为轻量级进程(LWP),是一个基本的CPU执行单元;它包含了一个线程ID、一个程序计数器、一个寄存器组和一个堆栈。它与属于同一个进程的其它的线程共享代码段、数据段,以及其它的操作系统资源(比如:打开的文件和信号)。一个传统的(或者说重量级)进程有一个单独的控制执行序列。如果一个进程有多个控制执行序列,那么它就能够同时进行多个任务。图5.1说明了传统的单线程进程和多线程进程的区别。
5.1.1 线程的起源
运行在现代桌面PC上的一些软件包是多线程的。应用程序通常被实现为一个拥有多个控制执行序列的独立的进程。一个网页浏览器可能会有一个线程来显示图片或文本,而同时有其它的线程从网络上获取数据。一个字处理器可能有一个线程显示图像,另一个线程读取用户按键,并且有第三个线程在后台执行拼写和语法检查。
在某些情况下,一个单独的应用程序可能需要执行几个相似的任务。例如,一个Web服务器接收到对网页、图片、声音等的客户端请求。流量比较高的服务器(busy web server)可能同时有很多(可能有数百个)客户端访问它。如果Web服务器作为一个传统的单线程进程运行,那么它同时只能够响应一个客户端。客户端等待服务的时间可能会非常长。
一种解决方案是使服务器作为一个单独的接收请求的进程来运行。当服务器接收到一个请求时,它就创建一个独立的进程来处理这个请求。事实上,这种创建进程的方法在线程广泛应用之前非常普遍。如上一章所述,进程的创建是重量级的。如果一个新进程要执行的任务与已存在进程的任务相同,那么何必要耗费所有这些开销呢?通常更高效的方法是创建一个包含了多个线程的进程来服务同样的请求。这种方法将使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 用户线程和内核线程
至此,我们依然以一种普通的认识来对待线程。然而,通常在用户级(用户线程)或内核级(内核线程)来提供对线程的支持。
?对用户线程的支持通常处于内核之上,通过一个用户级线程库(thread library)实现。线程库提供了对线程的创建、调度和管理的支持,这无需来自内核的支持。因为内核并不知道用户级线程的存在,所有的线程创建和调度工作都在用户空间完成,而且整个过程不受内核的干涉。所以,用户级线程的创建和管理通常很快;然而,它们也有一些缺点。例如:如果内核是单线程的,那么任何用户级线程执行一个导致阻塞的系统调用时就会导致整个进程阻塞,即使程序内部的其它线程可以运行。用户线程库包括了POSIX Pthreads、Mach C-threads和Solaris 2 UI-threads。
?内核线程由操作系统直接支持:内核在内核空间内实现了线程的创建、调度和管理。因为线程管理由操作系统完成,所以内核线程的创建和管理要比用户线程慢。然而,由于线程由内核管理,如果一个线程执行一个导致阻塞的系统调用,那么内核可以调度程序中的其它线程执行。同样,在多处理机环境中,内核能够在不同的处理器中调度线程。大多数现代操作系统都支持内核线程,包括: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线程。