您可以利用单元模式进程化,建立多线程的进程外部部件。这种部件提供的对象可以运行在不同的执行线程上。
为了将对象指派到进程外部件中的线程,Visual Basic 提供了三种模型。可以在“工程属性”对话框中设置“通用”选项卡的“无用户界面执行”框,选择其中的一种模型。
线程指派模型 | “无用户界面执行”的设置 |
单一的执行线程 | 选择“线程池”选项,指定线程数为一。 |
循环指派线程的线程池 | 选择“线程池”选项,并指定可用的线程数目。 |
每个在外部创建的对象有一个属于自己的线程 | 选择“每个对象的线程”选项。 |
注意 在运行时不能改变已经选定的模型。
和其它私有对象一样,窗体所驻留的线程就是创建该窗体的线程。私有对象不能用 CreateObject 函数创建,因此不能用它们来启动新的线程。
重点 在多线程的可执行程序中, Sub Main 是为每个新线程独立执行的。 Visual Basic 没有提供判断那个线程先被创建的方法。
当选择“无用户界面执行”时,单一的执行线程是缺省设置。使用该选项可以编译 Visual Basic 早期版本开发的部件,无需考虑线程。
如果使用“无用户界面执行”选项来重新编译部件,该服务器将不能再显示出消息框,也就无法获得管理员的干预。不过,这种消息可以被记录到 Windows NT 的事件日志(或另外选择的日志文件),请参阅“多线程部件的事件日志”。
当客户端请求对象时,Visual Basic 使用在线程池的下一个线程上创建所需的对象。当线程到达线程池的尾端时,就从头开始。
例如,图 8.6 中的部件带有三个可在外部创建的类(即类的 Instancing 属性被设置为除了 Private 和 PublicNotCreatable 之外的任何值)。这三个类分别为 A、B 和 C。为了便于说明哪个客户端请求了哪个对象,代表对象的圆圈中包含了类名和创建实例的客户端号。
图 8.6 带五个线程和四个客户端的循环线程池
有四个客户端创建了这些类的实例。客户端 1 请求了类 A 的第一个对象,该对象在线程 T1 上创建。客户端 2 也请求了类 A 的一个对象,该对象在线程 T2 上创建,依此类推。某些客户端创建了同一个类的多个实例,例如客户端 1 总共使用了类 A 的四个实例。
图 8.6 说明了循环线程池模型的一个重要问题:不可能预言哪些对象将位于同一个线程并因此共享全局数据。不同客户端使用的对象可能会共享全局数据,而同一个客户端使用的对象也可能不能共享全局的数据。
例如,在线程 T1 上的对象被客户端 1、2 和 3 使用,因此它们可以共享该线程的全局数据实例。客户端 1 使用的对象分布在线程 T1、T3、T4 和 T5 上。对这些对象来说,只有在线程 T5 上的两个对象能共享全局数据。
另外,在同一个线程上,对对象的属性和方法的调用将被序列化,并可能因此而互相阻塞。例如,如果客户端 1 先调用了它在线程 T1 上的 A 对象的方法,随后客户端 2 调用它在线程 T1 上的 B 对象的方法,那么客户端 2 就必须等待客户端 1 的调用执行完毕。
调用的序列化,以及全局数据的共享,可能带来严重的后果:客户端应用程序的设计人员无法预言何时对象可以共享全局数据,何时对象将互相阻塞。因此,循环线程池算法的行为被称为非确定性的。
循环线程池的另一个重要问题可以由图 8.7 说明。
图 8.7 循环线程池缺乏负载平衡机制
客户端 2 和 3 已经释放了各自的对象并已关闭。现在,线程 T1 上只有一个对象,线程 T2 上没有任何对象。然而,下一个对象仍将创建在线程 T3 上。实际上,要在线程 T3、T4 和 T5 上创建对象之后,才会为正在起作用的线程 T1 和 T2 创建对象!
作为对这些缺陷的补偿,循环线程池模型的优点是限制了线程的总数。这是个很重要的优点,因为,只有当活跃线程的总数与处理器的个数大致匹配的时候,多处理才会工作在最佳状态。
详细信息 在“部件设计的一般准则”的“可在外部创建的对象”中将论述可在外部创建的对象。
在客户端请求对象时,每个对象将创建在一个新的线程上。当最终客户端释放对某线程上的对象的最后一个引用后,该线程就终止。
请注意,客户端可能会引用一个线程上的几个对象。假设客户端创建了一个 Widget 对象,该对象又依次创建了两个从属对象 Sprocket 和 Gear。如果客户端得到对 Sprocket 对象的引用后释放了该 Widget 对象,该线程仍将存在,直至 Sprocket 对象被释放。
在这种方式下,如果保留了不被使用的线程,最终会导致系统性能下降。因此,在使用多线程部件时,避免对象引用悬空是十分关键的。
每个对象一个线程模型的最大缺陷就是对客户端创建的对象数目没有控制(因而无法限制线程的数目)。如果创建太多的线程,将使操作系统陷入维护线程的过重开销产生的停顿中。
太多的活动的线程(即代码正在执行的线程)将更快陷入使操作系统陷入瘫痪中。一般来说,活动的线程的数目应与处理器的数目相当,从而保证不会有空闲的处理器。如果仅有一个处理器,就不需要很多的活动的线程。
如果客户端请求一个可在外部创建的类的实例,Visual Basic 将在线程池的下一个线程上创建该对象,或者在一个新的线程上创建。不过,如果这个对象接着又请求一个可在外部创建的类的实例,那么,以后发生的情况将取决于第二个实例是如何创建的:
注意 如果一个对象使用 CreateObject 在另一个线程上创建另一个对象,对新创建的对象的属性和方法的任何调用都将进行跨线程调度。
详细信息 关于从属对象,请参阅“部件设计的一般准则”中的“从属对象”。在编写对象的属性和方法的代码时,遵守重入规则是非常重要的。请参阅“Visual Basic 的单元模型线程”。