指南:
|
方法 |
优点 |
缺点 |
---|---|---|
单进程,无线程 |
|
|
单进程,多线程 |
|
|
多进程 |
|
|
典型的演进路径是从单进程体系结构开始,再为需要同时发生的各组行为添加进程。在这些范围更广的组中,考虑附加的并发需求,在各进程中添加线程以提高并发性。
初始起点是将许多活动对象分配给单个操作系统任务或线程(使用依用途创建的活动对象调度程序),使用此方法虽然通常能够以单个操作系统任务或线程实现非常轻量级的并发模拟,但将不可能利用多 CPU 机器的优势。关键决策是在单独的线程中隔离分块行为,以便分块行为不会成为一个瓶颈。这将导致有分块行为的活动对象隔离到它们自己的操作系统线程中。
不幸的是,和许多体系结构决策一样,不存在简单的答案;正确的解决方案涉及经仔细权衡的方法。可以使用小型的体系结构原型来考察一组特定选择的影响。在建立进程体系结构的原型时,重点在于将进程数放大至系统理论最大值。请考虑以下问题:
活动对象可以以同步或异步方式互相通信。同步通信是很有用的,因为它可以通过使用严格控制的序列简化复杂的协作。即,当活动对象正在执行涉及同步调用其它活动对象的“运行至完成”步骤时,可以忽略所有由其它对象启动的并发交互,直到整个序列完成。
虽然这在某些情况中这很有用,但也可能产生问题,因为它有可能使一个更重要的高优先级事件也必须等候(优先级颠倒)。当同步调用的对象可能自身已阻塞,正等待对它自己的同步调用的响应时,此情况还会恶化。这会导致无限的优先级颠倒。在最极端的情况中,如果同步调用链中有循环,则会导致死锁。
异步调用通过启用有限的响应时间来避免此问题。但是,依赖于软件体系结构,异步通信通常导致更复杂的代码,因为活动对象随时都可能必须响应几个异步事件(每个事件都可能必须有与其它活动对象的复杂异步交互序列)。这在实施时可能非常困难并容易出错。
使用具有确定消息传递的异步消息传递技术可以简化应用程序编程任务。即使网络连接或远程应用程序不可用,应用程序仍可继续运作。异步消息传递并没有排除要以同步方式使用它的情况。同步技术将要求在应用程序可用时,就应有连接可用。因为已知有连接存在,所以处理提交处理可能更简单。
虽然活动对象的环境切换开销可能非常低,仍会有一些应用程序发现开销无法接受。在需要以高速率处理大量数据的情形中,通常会发生该情况。在这些情况中,可能必须回到使用被动对象和更传统(但风险更高)的并发管理技术(例如信号量)上。
但是这些考虑并不一定意味着必须完全放弃活动对象方法。即使在如此数据密集的应用程序中, 性能敏感的部分通常也只是整个系统中相对较小的一部分。这表示系统的其余部分仍可以利用活动对象范例。
通常,对于系统设计,性能只是设计标准之一。如果系统很复杂,那么其它诸如可维护性、易于更改和可理解性等的标准同样重要(就算不是更重要)。活动对象方法与低级别的特定于技术的机制相比有一个明显的优点,因为它在允许按照特定于应用程序的术语来表达设计的同时,隐藏了并发和并发管理的很多复杂性。
无交互的并发组件是一个几乎无关紧要的问题。几乎所有的设计问题都必须处理并发活动之间的交互,所以必须首先注重于理解交互。可提出的一些问题有:
一旦理解了交互,则可以考虑实施它的方法。所选的实施应产生最简单的设计,与系统的性能目标相一致。性能需求通常包括整体的吞吐量和在对外部生成的事件的响应中可以接受的等待时间。
在应用程序中到处嵌入关于外部接口的特定假设,这不是好的做法,而且让几个控制线程阻塞以等待某事件的方法非常低效。而是应该向一个对象指定检测事件的专门任务。当事件发生时,该对象就可以通知需要知道该事件的所有其它对象。此设计基于众所周知并且已被证实的设计模式-“观察器”模式([GAM94])。可以容易地将该模式扩展为具有更高灵活性的“发布者-预订者模式”,其中发布者对象作为事件检测器和对事件感兴趣的对象(“预订者”)之间的中介([BUS96])。
系统中的操作可能由发生了外部生成的事件所触发。一个非常重要的外部生成的事件可能是简单的已经过时间,表示为时钟的秒数。其它外部事件来自连接到外部硬件的输入设备,包括用户接口设备、进程传感器和与其它系统的通信链路。
为了使软件能检测到事件,必须将它阻塞以等待中断,或定期检查硬件以查看是否有事件发生。在后一种情况中,循环周期应比较短,以避免遗漏短生命期的事件或多个事件,或简单地最小化事件发生和检测之间的等待时间。
与此情况有关的一件有趣的事情是无论某事件如何罕见,某软件必须被阻塞以等待该事件或经常检查该事件。但系统必须处理的许多(如果不是大多数)事件都是很少见的事件;在任何给定的系统中,大多数情况下不会发生任何重大事件。
电梯系统为此情况提供了许多很好的示例。在电梯生命期中的重要事件有呼叫服务、乘客楼层选择、乘客的手阻挡电梯门以及经过一个楼层到下一个楼层。这些事件中的某些事件需要非常及时的响应,但与期望响应时间的时间范围相比,都是非常少见的。
单个事件可能触发许多操作,而各操作可能依赖于各种对象的状态。更进一步,不同的系统配置可能以不同的方式使用相同的事件。例如,当电梯经过一个楼层时,应更新电梯舱中的显示,并且电梯自己必须知道其位置以便知道如何响应新的呼叫和乘客楼层选择。可能在每个楼层都显示电梯位置,也可能不是这样。
轮询代价高昂;它需要系统的某些部分定期停止正在进行的操作以查看是否有事件发生。如果必须快速响应该事件,则系统必须经常检查事件是否已到达,进而限制了它可以完成的其它工作量。
如果分配一个中断给事件,再加上由该中断激活的事件相关代码,则要有效地多。虽然有时因为考虑到中断较为“昂贵”而避免使用它们,但明智地使用中断仍比重复的轮询要有效的多。
当事件随机到达或不经常到达时,应使用中断作为事件通知机制,因为这种情况下大多数轮询工作发现事件并未发生。当事件以定期或可预测的方式到达时,较适合使用轮询,因为大多数轮询工作将发现已发生事件。在这中间,有一个位置无论是轮询行为还是反应行为都没有区别,此时两种方法是等价的,选择何种方法无关紧要。但是在大多数情况中,真实世界中的事件是随机的,应优先使用反应行为。
广播数据(通常使用信号)代价高昂,并且通常是浪费的 - 可能仅有一些对象对该数据感兴趣,但所有(或许多)对象都必须停止以检查它。更好的、资源消耗更少的方法是使用通知以仅通知对已发生某事件感兴趣的那些对象。将广播仅限于那些需要很多对象注意的事件(通常是计时或同步事件)。
更具体地:
也许开发有效的并发应用程序最重要的准则就是最大程度地使用最轻量级的并发机制。硬件和操作系统软件在支持并发性中都扮演重要角色,但它们都提供了相对重量级的机制,将大量工作留给应用程序设计人员。我们要弥补可用工具和并发应用程序需求之间的巨大差距。
活动对象通过两个关键功能,帮助弥补该差距:
活动对象还为编程语言提供的被动对象提供了一个理想环境。完全从并发对象的基础设计系统,而没有诸如程序和进程之类的过程工件,可以实现更模块化、聚集、可理解的设计。
在大多数系统中,少于 10% 的代码将使用超过 90% 的 CPU 周期。
许多系统设计者设计时好像每行代码都必须进行优化。其实,应将时间花费在优化最常运行或耗时较长的 10% 代码上。对其它 90% 的代码设计应强调可理解性、可维护性、模块性及易于实施。
非功能需求和系统体系结构将影响用于实施远程过程调用的机制的选择。下面提供了各备选方案之间的各种权衡的概述。
机制 | 使用 | 注释 |
---|---|---|
消息传递 | 异步访问企业服务器 | 消息传递中间件通过处理队列、超时和恢复/重新启动条件,可简化应用程序编程任务。还可以以伪同步方式使用消息传递中间件。通常,消息传递技术可以支持大型消息尺寸。某些 RPC 方法可能限制消息大小,需要附加编程来处理大型消息。 |
JDBC/ODBC | 数据库调用 | 它们是不依赖于数据库的 Java Servlet 或应用程序接口,对在相同或另一服务器上的数据库进行调用。 |
本机接口 | 数据库调用 | 许多数据库供应商已实施了到他们自己的数据库的本机应用程序编程接口,这些接口以应用程序可移植性为代价提供了比 ODBC 更好的性能优势。 |
远程过程调用 | 调用远程服务器上的程序 | 如果您有一个应用程序构建器为您进行远程过程调用,则您可以不需要 RPC 级别的编程。 |
会话式 | 在电子商务应用程序中很少使用 | 通常是使用诸如 APPC 或套接字之类协议的低级程序到程序通信。 |
许多系统需要并发行为和分发式组件。大多数编程语言对这些问题只给予了很少帮助。我们已经知道,需要良好的抽象来理解应用程序中的并发需求,并理解用于在软件中实施并发的选择。我们还已经知道以下似非而是的事实:虽然并发软件本质上比非并发软件更复杂,它却能够极大地简化系统(这些系统必须处理真实世界中的并发)的设计。
Rational Unified Process
|