任务:描述运行时体系结构
此任务从活动类及其实例、它们与操作系统线程和流程的关系这几个方面为系统定义流程体系结构。
用途
  • 分析并行需求、
  • 确定进程及其生命周期、
  • 确定进程间的通信机制并分配进程间的协调资源、
  • 在进程间分布模型元素。
关系
角色主要: 其他: 辅助:
输入必需: 可选: 外部:
输出
主要描述

活动对象(即,活动类的实例)用于代表系统中的并发执行线程:从理论上来讲,每个活动对象都有其自己的控制线程,而且按照惯例是执行堆栈框架的根。将活动对象映射到实际的操作系统线程或流程可能因响应需求的不同而不同,并且将受环境切换开销考虑事项的影响。例如,与简单调度程序合并的一些活动对象可能会共享单个操作系统线程,从而造成并发运行的现象。但是,如果任何活动对象表现出阻塞行为(例如,通过执行同步输入-输出),那么组中的其他活动对象将无法对在操作系统线程被阻塞时发生的事件作出反应。

另一个极端是,如果处理资源不受额外环境切换开销的负面影响,那么对每个活动对象都给予它自己的操作系统线程应该会带来更高的响应度。

在实时系统中, 工作产品:封装体是将并行性建模时建议使用的方法;与活动类类似,每个封装体都有自己理论上的控制线程,但是封装体有其他的封装和合成语义,使复杂的实时问题的建模更具可跟踪性。

此任务从活动类及其实例、它们与操作系统线程和流程的关系这几个方面为系统定义流程体系结构。同样的,对于实时系统,流程体系结构将从封装体以及它们到操作系统进程和线程的关联映射的方面进行定义。

在“精化”阶段早期,该体系结构将是相当初步的,但到了“精化”阶段后期,进程和线程应该有了很好的定义。此任务的结果记录在设计模型中 - 更具体地说,是在流程视图中(请参阅概念:流程视图)。

步骤
分析并行需求
目的  定义系统并行执行要求达到什么程度。该定义将有助于塑造体系结构。 

任务:确定设计元素期间,考虑了主要由问题领域中自然产生的并行需要所决定的并行需求。 

这样得到的结果是一组活动类,代表系统中的逻辑控制线程。在实时系统中,这些活动类由 工作产品:封装体表示。

在本步骤中,我们考虑并行需求的其他来源 - 那些由系统的非功能需求强加的并行需求。

并行需求由以下内容驱使:

  • 系统必须分布的程度。其行为必须在处理器或节点间分布的系统实际上要求一个多流程的体系结构。使用某种“数据库管理系统”或“事务管理器”的系统还必须考虑那些主要子系统引入的流程。
  • 关键算法的计算密度。为了提供较好的响应时间,可能有必要将计算密集的活动放置在活动自己的进程或线程中,从而使系统在计算进行时仍能对用户输入作出响应,虽然使用的资源变少了。
  • 环境支持的并行执行的程度。如果操作系统或环境不支持线程(轻量级流程),就没有多少必要考虑它们对系统体系结构的影响了。
  • 系统中的容错需要。备份处理器要求有备份流程,并导致需要使主要流程和备份流程保持同步。
  • 系统中事件的到达模式。 在带有外部设备或传感器的系统中,传入事件的到达模式将因传感器而异。有些事件可能是周期性的(即,以固定的时间间隔发生,时间有少量的增加或减少),也可能是非周期性的(即,以不规则的时间间隔发生)。代表生成不同事件模式的设备的活动类通常将被分配到不同的操作系统线程,带有不同的调度算法,以确保不会错过事件或处理截止期限(如果这是系统要求的话)。这种推理同样适用于封装体 - 当封装体在实时系统的设计中使用时适用。

与许多体系结构问题一样,这些需求可能存在轻度的互斥。出现互相冲突的需求(至少一开始时)并不少见。根据需求的重要性对需求排序将有助于解决冲突。

确定进程和线程
目的  定义将在系统中存在的进程和线程。 

最简单的方法是将所有活动对象分配给公共线程或流程,并使用简单的活动对象调度程序,因为这样能将环境切换的开销降到最低。但是,在某些情况下,可能有必要将活动对象分布到一个或多个线程或流程之间。对大多数实时系统几乎肯定是这种情况,在实时系统中,用于代表逻辑线程的封装体有时必须满足严格的调度要求。

如果与其他活动对象共享操作系统线程的活动对象对某个其他的进程或线程发出了同步调用,而且该调用阻碍了调用对象的共享操作系统线程,那么这将自动暂挂位于调用流程中的所有其他的活动对象。现在就不一定会出现这种情况了:从活动对象的角度看是同步的调用在控制活动对象组的简单调度程序看来可能是不同步处理的 - 调度程序暂挂发出调用的活动对象(等待其同步调用的完成),然后调度其他活动对象运行。 

当原始的“同步”操作完成时,发出调用的活动对象就可以恢复了。但是,这种方法并非总是可用,因为它对于以下调度程序可能不可行:这样的调度程序被设计为拦截所有同步调用(不管这些调用会不会阻塞)。请注意,使用同一个操作系统进程或线程的活动对象之间的同步调用可能(在一般情况下)会被调度程序用这种方法进行处理 - 而且,在发出调用的活动对象看来,在效果上与过程调用相同。

这使我们得出这样的结论:活动对象应该根据它们与阻塞线程的同步调用并发运行的需要来分组为进程或线程。也就是说,活动对象应与另一个使用阻塞线程的同步调用的对象封装到同一个进程或线程中的唯一可能是:它不需要与该对象并发执行,并且可以容忍不在其他对象受阻时执行。在极端的情况下,当响应性很关键的时候,这可能会造成有必要为每个活动对象安排独立的线程或进程。

对于实时系统,封装体的基于消息的接口意味着能更简单地想到这样一个调度程序:它能确保(至少对于封装体之间的通信)支持的操作系统线程永远不会受阻,甚至当封装体与另一个封装体同步通信时也一样。 但是,封装体还是可能对操作系统直接发出会阻塞线程的请求,例如,请求同步计时等待。如果封装体要共享通用的线程(并使用简单的调度程序来模仿并行),那么对于由封装体调用的较低级别服务就必须建立约定来避免这种行为。

作为一个一般规则,在上述情形中,使用轻量级线程比使用成熟的进程更好,因为这样涉及的开销要少。但是在一些特殊的情况下,我们可能还是希望利用进程的一些特殊特征。既然线程共享同一个地址空间,它们内在地就比进程有更大的风险。如果担心意外覆盖的可能性,那么就最好选择进程。而且,因为进程在大多数操作系统中代表独立的恢复单元,所以根据活动对象彼此独立恢复的需要将它们分配给进程可能会很有用。也就是说,需要恢复为一个单元的所有活动对象都可以一起封装到同一个进程中。

请为系统需要的每一个独立的控制流创建一个进程或线程(轻量级进程)。 线程应该在需要嵌套的控制流的情况下使用(即,如果在进程内的子任务级别上需要独立的控制流)。

例如,独立的控制线程可能需要完成以下操作:

  • 区别软件不同区域之间的关系
  • 利用分布式系统中的一个或多个节点中的多个 CPU
  • 通过在控制线程被暂挂时将循环分配给其他活动来提高 CPU 的利用率
  • 区分活动的优先顺序
  • 支持数个进程和处理器之间的负载共享
  • 通过拥有备份进程来实现更高的系统可用性
  • 支持 DBMS、“事务管理器”或其他主要子系统。

示例

在自动柜员机中,必须处理来自三个不同来源的异步事件:系统的用户、ATM 设备(例如当自动取款机出现拥堵时)或者 ATM 网络(当网络发出关闭伪指令时)。要处理这些异步事件,我们可以在 ATM 本身中定义三个独立的执行线程,如下面用 UML 的活动类所显示。

ATM 进程和线程图

ATM 中的进程和线程

确定进程的生命周期
目的  确定进程和线程在何时创建与销毁。 

每个控制进程或线程都必须创建和销毁。但单进程体系结构中,进程在应用程序启动时创建,在应用程序结束时销毁。 在多进程体系结构中,新的进程(或线程)一般从在应用程序启动时由操作系统创建的第一个进程中衍生或派生。这些进程也必须明确地销毁。

必须确定并记录导致进程创建和销毁的事件的顺序(以及创建和删除的机制)。

示例

在自动柜员机中,会启动一个负责协调整个系统的行为的主要进程。它接着衍生一些下级控制线程来监视系统的各个部分:系统中的设备以及从客户和 ATM 网络发出的事件。这些进程和线程的创建可以用 UML 的活动类来显示,而这些活动类的实例的创建可以用时序图来显示,如下所示:

系统启动进程和线程创建图

系统初始化期间进程和线程的创建

确定进程间的通信机制
目的  确定进程和线程将交流的方法。 

进程间通信(IPC)机制使消息能够在独立进程中执行的对象间发送。

典型的进程间通信机制包括:

  • 共享内存,带或者不带确保同步的信号。
  • 会合,特别在直接由某种语言(如 Ada)支持时
  • 信号,用于阻止对共享资源的同时访问
  • 消息传递,既有“点到点”的,也有“点到多点”的
  • 邮箱
  • RPC - 远程过程调用
  • 事件广播 - 使用“软件总线”(“消息总线体系结构”)

IPC 机制的选择将改变系统建模的方式;例如,在“消息总线体系结构”中,对象之间不需要显式关联关系就可以发送消息。

分配进程间的协调资源
目的 分配稀缺资源
预测和管理潜在的性能瓶颈 

进程间的通信机制一般很稀有。信号、共享内存和邮箱一般大小或数量固定,而且不花大成本就不可能增加。RPC、消息和事件广播越来越多地吸收稀缺的网络带宽。当系统超过资源阈值时,它一般会经历非线性的性能下降:一旦稀缺资源用完了,那么后来对它的请求就可能会有不尽人意的效果。

如果稀缺资源不可用,则可以考虑几个策略:

  • 通过减少进程的数量来降低对稀缺资源的需要
  • 更改稀缺资源的用途(对于一个或多个进程,选择一个不同的、不太稀缺的资源用于 IPC 机制)
  • 增加稀缺资源的数量(例如,增加信号的数量)。这可以对相对较小的更改执行,但往往有副作用或者有固定的限制。
  • 共享稀缺资源(例如,仅在需要时分配资源,然后在用完后释放)。这样做很昂贵,而且可能只能预防资源危机。

一旦部署了系统,那么不管选择了什么策略,系统都应该温和地降级(而不是崩溃),而且应该向系统管理员提供足够的反馈,使问题当场得到解决(如果可能的话)。

如果系统要求运行时环境有特殊的配置才能提高关键资源的可用性(通常由重新配置操作系统内核来控制),那么系统安装就需要自动进行特殊配置,或者指示系统管理员这么做,系统才能变得可以操作。例如,系统可能需要重新引导,更改才能生效。

将进程映射到实施环境
目的 将“控制流”映射到受实施环境支持的概念上。 

概念进程必须映射到操作环境中的特定构造上。在许多环境中都可以选择进程类型,至少通常可以选择进程和线程。选择将根据耦合程度(进程是独立的,而线程在封闭进程的环境中运行)以及系统的性能要求(线程间的进程间通信一般比进程间的进程间通信更快速、更有效)来作出。

在许多系统中,每个进程可能有最大量的线程,或者每个节点有最大量的进程。这些限制可能不是绝对的,但可能是由于稀缺资源的可用性而强加的实际限制。需要考虑的是已经在目标节点上运行的线程和进程,以及进程体系结构中提议的线程和进程。较早的分配进程间的协调资源步骤的结果需要在映射完成时考虑,以确保没有引起新的性能问题。

将设计元素映射到控制线程
目的 确定类和子系统应该在哪些控制线程内执行。 

给定的类或子系统的实例必须至少在一个为类或子系统提供执行环境的控制线程内执行;它们实际上可以在多个不同的进程中执行。

我们同时用两个不同的策略,就可以确定“正确”的并行量,并可以定义“正确”的进程组:

从内到外

  1. 从“设计模型”开始,将类和子系统组成两种合作元素的集合:(a)互相紧密合作的元素以及(b)需要在同一个控制线程中执行的元素。 要考虑将元素分为独立的控制线程前将进程间通信引入消息序列中的影响。
  2. 相反,将根本没有相互作用的类和子系统分开,将它们放在独立的控制线程中。
  3. 这种群集划分将一直存在,直到进程的数量已经减到了仍能允许分布和使用物理资源的最小数量。

从外到内

  1. 确定系统必须作出响应的外部激励。定义一个独立的控制线程来处理每个激励,定义一个独立的服务器控制线程来提供每个服务。
  2. 考虑数据完整性和串行化约束,将这个初始控制线程集合减少到可以受执行环境支持的数量。

这不是会带来最佳进程视图的线性的、确定性的过程;它要求一些迭代达到可接受的妥协。

示例

下图说明 ATM 中的类如何在系统中的进程和线程间分布。

ATM 类在进程和线程间的分布图

ATM 的类到进程的映射



属性
多次出现
事件驱动
正在进行
可选
已计划
可重复
更多信息