操作系统——线程

一、引入多线程技术的动机

在传统的操作系统中,进程是系统进行资源分配的基本单位,按进程为单位分给存放其映象所需要的虚地址空间、执行所需要的主存空间、完成任务需要的其他各类外围设备资源和文件。同时,进程也是处理器调度的基本单位,进程在任一时刻只有一个执行控制流,通常将这种结构的进程称单线程(结构)进程(single threaded process) 。
首先来考察一个文件服务器的例子,当它接受一个文件服务请求后,由于等待磁盘传输而经常被阻塞,假如不阻塞可继续接受新的文件服务请求并进行处理,则文件服务器的性能和效率便可以提高,由于处理这些请求时要共享一个磁盘缓冲区,程序和数据,要在同一个地址空间中操作。这一类应用非常多,例如,航空售票系统需要处理多个购票和查询请求,这些信息都与同一个数据库相关;而操作系统在同时处理许多用户进程的查询请求时,都要去访问数据库所在的同一个磁盘。对于上述这类基于同数据区的同时多请求应用,用单线程结构的进程难以达到这一目标,即使能解决问题代价也非常高,需要寻求新概念、提出新机制。随着并行技术、网络技术和软件设计技术的发展,给并发程序设计效率带来了一系列新的问题,主要表现在:

  • 进程时空的开销大,频繁的进程调度将耗费大量处理器时间,要为每个进程分配存储空间限制了操作系统中进程的总数。
  • 进程通信的代价大,每次通信均要涉及通信进程之间或通信进程与操作系统之间的信息传递。
  • 进程之间的并发性粒度较粗,并发度不高,过多的进程切换和通信延迟使得细粒度的并发得不偿失。
  • 不适合并行计算和分布并行计算的要求,对于多处理器和分布式的计算环境来说,进程之间大量频繁的通信和切换,会大大降低并行度。
  • 不适合客户/服务器计算的要求。对于 C/S 结构来说,那些需要频繁输入输出并同时大量计算的服务器进程(如数据库服务器、事务监督程序)很难体现效率。

这就迫切要求操作系统改进进程结构,提供新的机制,使得应用能够按照需求在同一进程中设计出多条控制流,多控制流之间可以并行执行,多控制流切换不需通过进程调度;多控制流之间还可以通过内存区直接通信,降低通信开销。这就是近年来流行的多线程(结构)进程(multiple threaded process) 。如果说操作系统中引入进程的目的是为了使多个程序能并发执行,以改善资源使用率和提高系统效率,那么,在操作系统中再引入线程,则是为了减少程序并发执行时所付出的时空开销,使得并发粒度更细、并发性更好。这里解决问题的基本思路是:把进程的两项功能--“独立分配资源”与“被调度分派执行”分离开来,前一项任务仍由进程完成,它作为系统资源分配和保护的独立单位,不需要频繁地切换;后一项任务交给称作线程的实体来完成,它作为系统调度和分派的基本单位,会被频繁地调度和换,在这种指导思想下,产生了线程的概念。
传统操作系统一般只支持单线程(结构)进程,如 MS-DOS 支持单用户进程,进程是单线程的;传统的 UNIX 支持多用户进程,每个进程也是单线程的。目前,很多著名的操作系统都支持多线程(结构)进程,如: Solaris、 Mach、 SVR4、 OS/390、 OS/2、
WindowNT、 Chorus 等; JAVA 的运行引擎则是单进程多线程的例子。许多计算机公司都推出了自己的线程接口规范,如 Solaris thread 接口规范、 OS/2 thread 接口规范、Windows NT thread 接口规范等; IEEE 也推出了 UNIX 类操作系统的多线程程序设计标准 POSIX 1003.4a。事实上,线程概念不仅局限于操作系统中,在程序设计语言、数据库管理系统和其他一些应用软件中,也通过引入线程来改善系统和应用程序的性能,可以相信多线程技术在程序设计中将会被越来越广泛地采用。
 

二、多线程环境中的进程与线程


1、多线程环境中的进程概念
 

1、多线程环境中的进程概念在传统操作系统的单线程进程中,进程和线程概念可以不加区别。下图 给出了单线程进程的内存布局和结构,它由进程控制块和用户地址空间,以及管理进程执行的调用/返回行为的系统堆栈或用户堆栈构成。一个进程的结构可以划分成两个部分:对资源的管理和实际的指令执行序列。显然,采用并发多进程程序设计时,并发进程之间的切换和通信均要借助于操作系统的进程管理和进程通信机制,因而,实现代价较大,而较大的进程切换和进程通信代价,又进一步影响了并发的粒度。

单线程进程的内存布局

操作系统——线程_第1张图片

 

设想是否可以把进程的管理和执行任务相分离,如下图 所示,让进程是操作系统中进行保护和资源分配的单位,允许一个进程中包含多个可并发执行的控制流,这些控制流切换时不必通过进程调度,通信时可以直接借助于共享内存区,每个控制流称为一个线程,这就是并发多线程程序设计。

管理和执行相分离的进程

操作系统——线程_第2张图片

 

多线程进程的内存布局如下图所示,在多线程环境中,仍然有与进程相关的内容是 PCB 和用户地址空间,而每个线程除了有独立堆栈,以及包含现场信息和其他状态信息外,也要设置线程控制块 TCB(Thread Control Block)。线程间的关系较为密切,一个进程中的所有线程共享其所属进程拥有的资源,它们驻留在相同的地址空间,可以存取相同的数据。例如,当一个线程改变了主存中一个数据项时,如果这时其他线程也存取这个数据项,它便能看到相同的结果。

多线程进程

操作系统——线程_第3张图片

最后,给出多线程环境中进程的定义:进程是操作系统中进行保护和资源分配的基本单位。它具有:

  • 一个虚拟地址空间,用来容纳进程的映像;
  • 对处理器、其他(通信的)进程、文件和 I/O 资源等的有控制有保护的访问。
  • 而传统进程原先所承担的控制流执行任务交给称作线程的部分完成。

 

2、多线程环境中的线程概念
 

线程是操作系统进程中能够独立执行的实体(控制流),是处理器调度和分派的基本单位。线程是进程的组成部分,每个进程内允许包含多个并发执行的实体(控制流),这就是多线程。同一个进程中的所有线程共享进程获得的主存空间和资源,但不拥有资源。线程具有:

  • 线程执行状态(运行、就绪、等待、…);
  • 当线程不运行时,有一个受保护的线程上下文,用于存储现场信息。所以,观察线程的一种方式是运行在进程内一个独立的程序计数器;
  • 一个执行堆栈
  • 一个容纳局部变量的主存存储区。

线程的主要特性是:

  • 并发性:同一进程的多个线程可在一个/多个处理器上并发或并行地执行,而进程之间的并发执行演变为不同进程的线程之间的并发执行。
  • 共享性:同一个进程中的所有线程共享但不拥有进程的状态和资源,且驻留在进程的同一个主存地址空间中,可以访问相同的数据。所以,需要有线程之间的通信和同步机制,但通信和同步的实现十分方便。
  • 动态性:线程是程序在相应数据集上的一次执行过程,由创建而产生,至撤销而消亡,有其生命周期,经历各种状态的变化。每个进程被创建时,至少同时为其创建一个线程,需要时线程可以再创建其他线程。
  • 结构性:线程是操作系统中的基本调度和分派单位,因此,它具有唯一的标识符和线程控制块,其中应包含调度所需的一切信息。
     

如下图所示,进程可以划分为两个部分:资源集合和线程集合。进程要支撑线程运行,为线程提供地址空间和各种资源,它封装了管理信息,包括对指令代码、全局数据和 I/O 状态数据等共享部分的管理。线程封装了执行信息,包括对 CPU 寄存器、执行栈(用户栈、内核栈)和局部变量、过程调用参数、返回值等线程私有部分的管理。由于线程具有许多传统进程所具有的特征,所以,也把线程称为轻量进程LWP(Light-Weight Process)。

线程的内存布局

操作系统——线程_第4张图片

下面用一个日常生活中的例子来说明多线程结构进程可以进一步提高系统的并发性。某大厦的装璜工程可作为一个“进程”运行,下有许多工程队,如瓦工队、木工队、水电工队、油漆工队等,每个工程队作为一个“线程”运行。“进程”负责采购资源(源料)和工程管理,有源料时这些工程队可以按进度齐头并进同时工作(多线程并行执行),以加快装璜进度。缺少源料时,如缺少水泥、木材、水管、油漆之一时,相应工程队等待(线程被阻塞),而可以调度其他工程队(线程)工作。从而,提高了整个系统(装璜工程)的并发性,加快了工程进度。
 

3、线程的状态
 

和进程类似,线程也有生命周期,因而,也存在各种状态。从调度需要来说,线程的关键状态有:运行、就绪和等待。另外,线程的状态转换也类似于进程。由于线程不是资源的拥有单位,挂起状态对线程是没有意义的,如果一个进程挂起后被对换出主存,则它的所有线程因共享了进程的地址空间,也必须全部对换出去。可见由挂起操作引起的状态是进程级状态,不作为线程级状态。类似地,进程的终止会导致进程中所有线程的终止。
进程中可能有多个线程,当处于运行态的线程执行中要求系统服务,如执行一个I/O 请求而成为等待态时,那么,多线程进程中,是不是要阻塞整个进程,即使这时还有其他就绪的线程?对于某些线程实现机制,所在进程也转换为阻塞态;对于另一些线程实现机制,如果存在另外一个处于就绪态的线程,则调度该线程处于运行状态,否则进程才转换为等待态。显然前一种做法欠妥,丢失了多线程机制的优越性,降低了系统的灵活性。
多线程进程的进程状态是怎样定义的?由于进程不是调度单位,不必划分成过细的状态,如 Windows 操作系统中仅把进程分成可运行和不可运行态,挂起状态属于不可运行态。
 

4、线程管理和线程包(库)
 

多线程技术是利用线程包(库)来提供一整套有关线程的原语集来支持多线程运行的,有的操作系统直接支持多线程,而有的操作系统不支持多线程。因而,线程包(库)可以分成两种:用户空间中运行的线程包(库)和内核中运行的线程包(库)。一般地说,线程包(库)至少应提供以下功能的原语调用:孵化、封锁、活化、结束、通信、同步、互斥等,以及切换(保护和恢复线程上下文)的代码,调度(对线程的调度算法及实施处理器调度)的代码。同时应提供一组与线程有关的应用程序编程接口 API,支持应用程序创建、调度、撤销和管理线程的运行。

基本的线程控制原语有:

  • 孵化(spawn):又称创建线程。当一个新进程被生成后,该进程的一个线程也就被创建。此后,该进程中的一个线程可以孵化同一进程中的其他线程,并为新线程提供指令计数器和参数。一个新线程还将被分配寄存器上下文和堆栈空间,并将其加入就绪队列。
  • 封锁(block):又称线程阻塞或等待。当一个线程等待一个事件时,将变成阻塞态,保护它的用户寄存器、程序计数器和堆栈指针等现场。处理器现在就可以转向执行其他就绪线程。
  • 活化(unblock):又称恢复线程。当被阻塞线程等待的事件发生时,线程变成就绪态或相应状态。
  • 结束(finish):又称撤销线程。当一个线程正常完成时,便回收它占有的寄存器和堆栈等资源,撤销线程 TCB。当一个线程运行出现异常时,允许强行撤销一个线程。
     

对于在用户空间运行的线程包(库),由于它完全在用户空间中运行,操作系统内核对线程包(库)不可见, 而仅仅知道管理的是一般的单线程进程。 这种情况下, 线程包(库)起到一个微内核的作用,实质上是多线程应用程序的开发和运行支撑环境。优点是:节省了内核的宝贵资源,减少内核态和用户态之间的切换,因而,线程(包)库的运行开销小效率高;容易按应用特定需要选择进程调度算法,也就是说,线程库的线程调度算法与操作系统的低级调度算法是无关的;能运行在任何操作系统上。缺点是:当线程执行一个系统调用时,不仅该线程被阻塞,而且,进程内的所有线程会被阻塞,这种多线程应用就不能充分利用多处理器的优点。
在内核中运行的线程包(库),则是通过内核来管理线程包(库)的。内核中不但要保存进程的数据结构,也要建立和维护线程的数据结构及保存每个线程的入口,线程管理的所有工作由操作系统内核来实现。由内核专门提供一组应用程序编程接口(API),供开发者开发多线程应用程序。优点是:能够调度同一进程中多个线程同时在处理器上并行执行,充分发挥多处理器的能力,若进程中的一个线程被阻塞了,内核能调度同一进程的其他线程占有处理器运行,也可以调度其他进程中的线程运行。缺点是:在同一进程中, 控制权从一个线程传送到另一个线程时需要用户态-内核态-用户态的模式切换,系统开销较大。
基于上述两种线程包(库)可把线程的实现分成三类:用户级线程 ULT(User LevelThread) (如 POSIX 的 P-threads、Java 的线程库)和内核级线程 KLT(Kernel Level Thread)(如 Windows2000/XP 、 OS/2 和 Mach C-thread)。也有一些系统(如 Solaris)提供了混合式,同时支持 ULT 和 KLT 两种线程的实现。下面将会进一步讨论三种线程包(库)的实现方法。
近年来,已出现了具有支持多线程运行的微处理器体系结构,称为超线程技术。如现在的多核处理器,是具有超线程技术的微处理器。它的微处理器包含两个逻辑上独立的微处理器,能够同时执行两个独立的线程代码流。每个均可被单独启停、中断和被调度执行特定的线程,而不会影响芯片上另一个逻辑上独立的处理器共享微处理器内核的执行资源,包括:引擎、高速 cache、总线接口和固件等。
 

5、并发多线程程序设计的优点


在一个进程中包含多个并行执行的控制流,而不是把多个可并发执行的控制流一一分散在多个进程中,这是并发多线程程序设计与并发多进程程序设计的不同之处。并发多线程程序设计的主要优点是使系统性能获得很大提高,具体表现在:

  • 快速线程切换。进程具有独立的虚地址空间,以进程为单位进行任务调度,系统必须交换地址空间,切换时间长,而在同一进程中的多线程共享同一地址空间,因而,能使线程快速切换。
  • 减少(系统)管理开销。对多个进程的管理(创建、调度、终止等)系统开销大,如响应客户请求建立一个新的服务进程的服务器应用中,创建的开销比较显著。面对创建、终止线程,虽也有系统开销,但要比进程小得多。 Mach开发者的研究表明,与没有使用线程的 UNIX 相比,进程创建的速度提高了10 倍。
  • (线程)通信易于实现。为了实现协作,进程或线程间需要交换数据。对于自动共享同一地址空间的各线程来说,所有全局数据均可自由访问,不需什么特殊设施就能实现数据共享。而进程通信则相当复杂,必须借助诸如通信机制、消息缓冲、管道机制等设施,而且还要调用内核功能才能实现,同时线程通信的效率也很高。
  • 并发程度提高。许多多任务操作系统限制用户能拥有的最多进程数目,如早期 UNIX 一般为 50 个,这对许多并发应用来说是不够的。而对多线程技术来说,一般可达几千个,基本上不存在线程数目的限制。
  • 节省内存空间。多线程合用进程地址空间,而不同进程独占地址空间,使用不经济。
  • 由于队列管理和处理器调度是以线程为单位的,因此,多数涉及执行的状态信息被维护在线程数据结构中。然而,存在一些影响到一个进程中所有线程的活动,操作系统必须在进程级进行管理。挂起意味着将主存中的地址空间对换到磁盘上,因为,在一个进程中的所有线程共享同一地址空间,此时,所有线程也必须进入挂起状态。相似地,终止一个进程时,所有线程应被终止。
     

6、线程组织和多线程技术的应用


一个进程中可包括若干线程,每个线程拥有自己的程序计数器和堆栈,用来跟踪程序运行的轨迹,这些线程分享了进程的处理器时间配额。在单处理器系统中,一个线程先执行,接着同一进程或其他进程中的另一个线程执行,这很象分时系统的做法。在多处理器系统中,同一进程或不同进程中的多个线程真正并行执行。一个进程中的所有线程都在同一个地址空间中,共享全局变量和各种资源,如打开文件、定时器、信号量、内存区。
进程中的线程可有多种组织方式:第一种是调度员/工作者模式,进程中的一个线程担任调度员的角色接受和处理工作请求,其他线程为工作者线程。由调度员线程分配任务和唤醒工作者线程工作。第二种是组模式,进程中的各个线程都可以取得并处理该请求,不存在调度者线程。有时每个线程被设计成专门处理特定的任务,同时建立相应的任务队列。第三种是流水线模式,线程排成一个次序,第一个线程产生的数据传送给下一个线程处理,依次类推,数据沿排定的卜次序由线程依次传递以完成请求的任务。
多线程技术在现代计算机软件中得到了广泛的应用,取得了较好的效果。下面举例说明多线程技术的一些主要应用:

  • 前台和后台工作。如在一个电子表格软件中,一个线程执行显示菜单和读入用户输入,同时,另一个线程执行用户命令和修改电子表格。
  • C/S 应用模式。局域网上文件(网络)服务器处理多个用户文件(任务)请求时,创建多个线程,若该服务器是多 CPU 的,则同一进程中的多线程可以同时运行在不同 CPU 上。
  • 加快执行速度。一个多线程进程在计算一批数据的同时,读入设备(网络、终端、打印机、硬盘)上的下一批数据,而这分别由两个线程实现。
  • 异步处理。程序中的异步成分可用线程实现,例如,为避免掉电带来损失,可把文字编辑器设计成周期性把内存缓冲内容写入到磁盘中。可以创建一个线程完成周期性写盘任务,该线程由操作系统调度,并不需要应用进程提供代码来做检查或输入输出。
  • 设计用户接口。每当用户要求执行一个动作时,就建立一个独立线程来完成这项动作。当用户要求有多个动作时,就由多个线程来实现,窗口系统应有一个线程专门处理鼠标的动作。例如, GUI 中,后台进行屏幕输出或真正计算;同时,要求对用户输入(鼠标)做出反映。有了多线程,可同时运行 GUI输入线程和后台计算线程,便能实现这一功能。
     

 

 

 

本文主要来自孙钟秀老师主编的《操作系统教程》书本。

你可能感兴趣的:(操作系统)