首页 新闻 论坛 群组 Blog 文档 下载 读书 Tag 网摘 搜索 开源 FAQ 第二书店 博文视点 程序员
频道: 研发 数据库 中间件 信息化 视频 .NET Java 游戏 移动 服务: 人才 外包 培训
    图书品种:235680
       
热门搜索: ASP.NET Ajax Spring Hibernate Java

5.2  进程与线程

第5.1节介绍了“并发式服务器”的优点,而“并发式服务器”可以通过多个进程(process)或线程(thread)来实现。这一设计空间涉及的主要取舍包括:健壮性、效率和可伸缩性。

多进程。进程是一种OS实体(entity),用以提供“执行程序指令”的环境(context)。每一个进程都管理某些资源,如虚拟内存、I/O句柄、信号处理器等,并借助内存管理单元(MMU,memory management unit)硬件,防止自身遭受其他OS进程破坏。在UNIX中,进程是通过fork()创建的,Win32进程则通过CreateProcess()创建;这些进程并发执行,但位于“和调用者不同”的地址空间。这些机制在第8章作了详尽介绍。

在上一代操作系统(如BSD UNIX [MBKQ96])中,进程只能控制一个线程。这种“单线程”的进程模型可以促进程序的健壮性;因为如果没有程序员明确介入,进程之间不会相互干扰。例如,进程只能通过“共享内存”或“本地IPC”机制相互合作,如图5.2(1)所示。

图5.2  多进程与多线程

但是,通过“单线程”进程难以开发某些类型的应用程序,特别是高性能服务器或实时服务器。需要相互通信或需要响应“管理请求”的服务器必须使用某种形式的IPC,这增加了它们的复杂性。此外,如果使用多进程,将难以对“调度”和“进程优先级”实施高效率、高精

度的控制。

多线程。为了减轻上面提及的进程存在的问题,如今,大多数OS平台都能在一个进程中支持多个线程。一个线程是一组单独的指令序列,执行在进程的保护范围之内,如图5.2(2)所示。除了一个指令指针(instruction pointer)之外,线程还要管理某些资源,例如,保存“函数活动记录”的运行时栈(stack)、一组寄存器、信号屏蔽设备、优先级以及和线程有关的数据等等。如果有多个CPU,多线程服务器中的服务则可以并行(parallel)执行[EKB+92]。在很多UNIX版本中,线程由pthread_create()创建;在Win32中,线程由CreateThread()创建。

在并发式网络应用程序的实现中,如果多个操作在各自的“线程”中执行,而不是在各自的“进程”中执行,以下“并发”开销就能得以降低。

l  线程创建与环境切换。和进程相比,线程维护的状态信息要少,因而,和对应的进程的生命活动相比,“线程创建”和“环境切换”的开销要少。例如,在一个进程中切换线程时,进程范围内的资源(如虚拟地址映射和缓存)不需要改变。

l  同步。在调度、执行一个应用程序线程时,核心模式和用户模式之间的切换可能不必要。而且,“进程内”同步的开销通常没有“进程间”同步昂贵,因为“被同步对象”位于进程内部,因而不需要OS内核的干预。相反,“进程间”线程同步一般需要牵涉到OS内核。

l  数据复制。线程可以通过“进程局部内存(process-local memory)”共享信息,这具有以下优点。

1.        较之通过“共享内存”或“本地IPC”机制进行进程间通信,通过“进程局部内存”通信往往更有效率;因为数据不需要通过内核来复制。

2.        在“进程局部内存”中,使用C++对象将更为容易,因为类的虚函数表不会存在“内存布局”问题(见副栏3)。例如,有这样一些相互协作的数据库服务,它们都引用了“进程局
 

部内存”中的公共数据结构,那么,在实现这些数据库服务时,使用“多线程”比使用“多进程”更简单、更高效。

有了这些优化,多线程可以显著地提高应用程序的性能。例如,多线程会使“I/O操作频繁”的应用程序受益,因为“计算密集(compute-intensive)”型服务可以同磁盘和网络操作重叠执行。但是,仅仅因为OS平台支持线程,并不就意味着所有应用程序都应该使用多线程。特别是,使用“多线程”实现“并发式”应用程序,也存在以下局限性。

l  性能损失。一个常见的误解是:线程天生就是用来提高程序性能的。其实在很多时候,线程并不能提高性能,原因如下。

1.        在单处理器上,“计算任务繁重”的应用程序不会从多线程受益,因为计算和通信无法并行执行。

2.        高精度的锁定策略会带来高同步开销,这会阻碍应用程序充分挖掘和利用“并行处理”[SS95]的好处。

l  健壮性降低。为减少“环境切换”和“同步”开销,线程之间接收到的“MMU保护”很少或没有。通过“单进程地址空间”中的线程执行所有任务,会降低应用程序的健壮性。原因如下。

1.        在同一进程地址空间中,各个线程之间缺乏很好的保护。因此,只要进程中有一个服务出错,全局数据就有可能被破坏;而这个数据可能会被“运行在这个进程中的其他线程上”的服务所共享。这就会造成不正确的执行结果,或使整个进程崩溃,或使应用程序无限期挂起。

2.        在一个线程中调用某些OS函数,会对整个进程带来不利的副作用(side effect)。例如,UNIX的exit()和Win32的ExitProcess()函数具有的副作用是:它们会终止一个进程中的所有线程。

l  缺乏高精度的访问控制。在大多数操作系统上,进程是“访问控制(access control)”的基本粒度(granularity)。因此,多线程存在另一个限制:在同一进程中,所有线程都共享相同的用户ID,共享相同的“对文件和其他被保护资源”的访问权。为了防止无意或有意地访问“未经授权的资源”,那些将“安全机制”建立在“进程所有权”上的网络服务,例如

TELNET,通常运行在单独的进程上。

日志服务程序 => 在我们的网络日志服务程序中,日志服务器的“并发”版本可以通过各种方式实现。在第8章和第9章,我们分别通过“多进程”和“多线程”来实现并发日志服务器。

查看所有评论(0)条】

最近评论



正在载入评论列表...
热点评论