线程模型

2024-10-25

线程模型(精选5篇)

线程模型 篇1

0 引言

在现代应用程序中, 为了用户界面的更加友好, 程序运行的更加流畅, 使用多线程进行任务的处理已经是主流的选择。但是多线程程序开发中, 我们往往会遇到以下问题:

(1) 在访问共享的数据时, 使用加锁方式实现。但这种方式在程序规模增长到一定程度后, 会不可避免地带来程序的低效、死锁等问题。 (2) 简单地为每个耗时的任务创建一个线程;这种模式首先造成资源的浪费, 其次当大量线程被创建出后, 会耗尽系统的资源从而导致系统变慢或死机;即使是正常关闭, 大量线程在关闭时的等待也将是一个漫长的过程。 (3) 任务执行完后, 通过回调通知发起者任务, 但可能发起者已被销毁, 从而导致程序的崩溃。

采用线程模型, 及约定开发人员以事先制定的模式工作, 能避免上述问题。线程模型的设计目标概括为以下几点:

(1) 除了在线程模型的管理和调度模块, 整个应用程序避免使用任何形式的锁。这样能避免程序低效、死锁等问题。 (2) 线程模型应提供能动态调整的线程池来执行用户提交的任务。 (3) 线程模型应提供统一的接口让用户提交任务、设定定时任务、设定任务过期条件、取消任务、存储任务和提供回调等, 避免在每个模块做同样的工作。 (4) 合理调度任务, 保证共享数据的安全。 (5) 线程模型应管理每个服务和回调的生命周期, 使应用程序避免崩溃。

如图1所示, 用户任务通过线程模型接口提交, 提交过程是异步的, 可立即返回;任务通过线程模型接口, 添加到服务存储和管理队列;任务分派程序会选择合适的任务提交到线程池中, 执行用户的任务, 完成时调用回调函数;通过生命周期管理, 当回调的对象销毁时, 自动取消回调。

1 线程模型的组成部分及其功能

一个完整的线程模型至少应包含以下组成部分:

(1) 线程模型接口; (2) 任务存储和调度线程的管理; (3) 任务的分派; (4) 线程池的管理; (5) 线程模型的辅助设施。

1.1 线程模型的接口

1.1.1 创建线程模型管理

线程管理模块在主程序入口处被创建。确保用户的各个模块都能调用到线程模型的各种接口。

1.1.2 销毁线程模型管理

线程管理模块在主程序出口处被销毁。销毁时, 线程管理模块保证正在被执行的任务执行完, 同时取消正在队列中等待的所有任务。

1.1.3 添加一个新的任务

线程模型提供辅助设施来协助用户创建任务。任务将有6个属性:command, callback task_id, group_id, priority和timeout。

1.1.4 设定任务分组

如果有些任务需要访问共享的数据, 这些任务将按顺序被放入线程池执行, 避免多线程同时访问共享数据。通过给这些任务赋予相同的ID, 任务分派模块就会自动的将相同ID的任务顺序放入线程池。

1.1.5 设定任务回调

任务回调在任务执行完成后被工作线程调用, 回调过程如需线程切换, 可通过线程模型的辅助设施来实施。

1.1.6 设置任务优先级

每项任务都有自己的优先级, 高优先级的任务将优先被放进线程池执行。

1.1.7 任务超时

有的任务会有一个执行的期限, 如超过这个期限, 回调函数将被执行。

1.1.8 取消任务

用户发起任务后, 在等待任务执行的过程中, 可能需要取消任务。此时, 如任务在等待的队列中, 任务将被删除。如任务已在线程池中被执行, 则断开该任务连接的回调函数。

1.1.9 重试任务

在某个任务失败后, 回调函数会通知用户此次执行失败和失败的原因。用户可通过重试该项任务, 重新将任务发送到存储队列中等待执行。

1.1.1 0 预约任务

用户希望在一段时间后启动某些项任务, 需要接口支持预约任务。

1.2 任务存储和调度线程的管理

1.2.1 任务及其回调的存储

采用多索引容器 (boost::multi_index_container) 形式来存储任务, 同时按照task id, priority和group id为任务建立不同的索引。这样, 不但在查找相应任务时效率更高, 而且也保证了插入或者删除数据时候的效率。[1]

1.2.2 任务管理及回调

(1) 由于用户可能在任意的线程调用线程模型, 来添加希望的服务。为避免本文开始提到的对共享数据加锁的问题, 需将任务的添加工作切换到任务管理线程执行。 (2) 维护用户任务的状态, Scheduling, Pending or Processing也需在管理线程进行。 (3) 在回调发生的时候, 需移除相应任务并触发用户预先设定的回调。回调必须在管理线程中执行, 需检查管理线程中该任务是否被取消。 (4) 用户可能不断添加新任务, 线程池会添加任务完成的事件到管理线程。同时, 用户可能会取消之前添加的任务。以上操作会影响到共同数据, 因此必须按顺序执行。但这样会导致大量添加新任务的操作, 导致分派任务一直无法得到执行;在这种情况下, 管理线程一直处于忙碌状态, 但是线程池却处于空闲状态。因此, 对于不同的任务的添加, 也需设定优先级。一般来说, 完成任务的优先级设为Medium, 用户取消任务的优先级设为High, 而添加任务的优先级设定为Low。

1.2.3 防止任务无限制占据线程

对线程模型来说, 用户创建的任务是不可控的。因此, 会发生由于用户任务错误导致线程池的线程进入死循环, 使得线程丧失继续服务的能力。线程调度管理程序如不能及时发现死去的线程, 将有可能导致线程池所有线程被占用, 从而导致用户所有任务均无法执行。一般可以记录上次该线程回调发生的时间。如超过指定时间范围而无响应, 可强制该线程关闭后重启或者关闭相应任务, 并重新添加线程到线程池。

1.2.4 定时器组件

为实现用户预约任务, 必须实现Timeout部件, 并在到期时, 将回调的执行过程控制在管理线程中。实践中, 可考虑用Boost::asio::deadline_timer。

1.2.5 内存池的管理

当等待任务多时, 增加线程池中线程的数量。当等待队列很少或为空时, 减少线程数量。增加减少不宜太频繁。一般根据一段时间内处理的任务数来决定开启的线程数。

1.2.6 任务回调的生命周期管理

对于回调任务, 一般需做两件事。第一, 确保回调发生在指定线程。这一点, 1.4节将会专门讲述。第二, 确保回调所依赖的对象存在;如所依赖的对象已被销毁, 那么就取消该回调。实现可采用boost::signal模式, 只要求回调所依赖的目标对象从boost::signals::trackable[2]派生即可。

1.3 任务的分派

任务的分派需要遵循以下原则:

(1) 分派单元的运行需确保在任务管理线程中执行。 (2) 分派单元按优先级取任务, 放入队列中执行。 (3) 如果标记为某个group id的任务已在线程池中运行, 那么该任务结束前, 同样group id的任务不能被再次放入。 (4) 添加任务时和complete task时均可尝试重新分派任务。

1.4 线程池的管理

线程池需提供的功能有:

(1) 启动指定数目的线程。 (2) 任务能够通过接口添加到线程池的队列中。 (3) 运行时动态增减线程数量。 (4) 退出时确保运行中的任务执行完毕。

1.5 线程模型的辅助设施

线程模型需要提供的辅助设置有:

(1) 创建任务。 (2) 创建回调命令。 (3) 提供Factory机制, 使目标线程可以注册相应的命令到Factory。该命令可将任意命令切换到线程执行。

2 线程模型的执行过程

线程模型执行的过程如图2所示。

线程模型的使用者通过接口创建线程模型并拿到需要的接口。通过线程模型提供的辅助函数生成任务后, 调用线程模型接口, 把任务添加到线程模型管理的任务队列。管理线程, 在任务队列不为空时, 选择合适的任务, 并将完成任务的事件和任务命令绑定。将组装好的命令放入线程池中去运行。执行完毕后, 完成任务的事件被触发, 并切换到管理线程。该事件将进行下一轮任务分派。

3 线程模型优化策略

对于不同的应用场合, 线程模型有着不同的优化策略。优化策略一般考虑的环节有:

(1) 是否充分利用每个线程的执行能力。 (2) 是否最大限度地减少了任务在线程之间的调度。 (3) 有些任务只读共享的数据, 有些需写那些数据。如果能将读写任务区分对待, 那么读数据的任务就可以同时添加到线程池中。 (4) 调度管理程序处理添加和完成任务的优先顺序, 及任务的存储结构。 (5) 任务队列的动态规划。

4 总结

无论在客户端UI编程, 还是在服务端编程, 线程模型都是一个非常重要的设施, 能提高程序的稳定性和可维护性。对于规模较大的系统, 这是一项非常重要的基础设施。本文结合在工程中的实践经验, 详尽分析了设计一个线程模型时需考虑的目标、结构、接口及模型的工作流。实践中, 这种线程模型能帮助应用程序简化设计, 提高稳定性, 提升效率。

摘要:本文详细描述了在实际商业系统上的通用的线程模型, 通过阐述其设计目标、工作原理、组成结构以及优化策略, 详尽的对线程模型进行了解读, 同时阐明了使用线程模型的优点和必要性。

关键词:线程模型,BOOST,ASIO,线程池

参考文献

[1]王凤岭.分布式操作系统中线程包实现方法的对比研究[J].南宁职业技术学院学报, 2004 (04) .

[2]陈矫阳, 陈楸, 刘桓龙.基于LabWindows/CVI多线程数据采集的研究[J].科学技术与工程, 2008 (09) .

[3]周仕祥, 刘伯恕.Boost功率因数校正器的效率和空载损耗研究[J].电力电子技术, 2003 (03) .

[4]肖和平, 韩伟红, 贾焰, 吴泉源.StarCCM2.0中高性能线程池模型的研究与实现[J].计算机工程, 2005 (24) .

多线程处理器发展浅析 篇2

关键词:多核心 处理器 多线程 超线程

中图分类号:TP338文献标识码:A文章编号:1674-098X(2012)01(a)-0018-01

所谓多线程,就是一种基于计算机软、硬件系统的处理技术,是具备并行处理多任务处理能力的计算平台。多线程技术也可以用于区别各种任务,以便分配给更多对时间比较敏感的流量的优先权。在相当长的一段时间内基于硬件的多线程技术其实已经存在了,如果用户在日常工作中经常接触高性能的服务器、工作站,对于经常使用的四路、八路、十六路甚至三十二路或者更多的处理组合,这就不是什么新的技术。通俗的讲,就是利用多颗处理器搭建起来的计算平台来处理多任务的多线程技术。处理器是独立的,又通过总线进行数据交换,从这看又是并行的处理结构,并行的处理结构能同时处理多个线程。

在20世纪后期,计算机平台处理能力的基本单位是以处理器的颗数来决定的,一颗处理器只能达到处理一个线程的处理能力。在出现面向以工作站、服务器领域为工作平台的系统软件也是根据处理器的数量进行分类的,系统软件也是以处理器的数量衡量性能。几路处理器就需要安装相应的能支持处理器数量的系统软件。为了能使处理器充分的榨取性能来数据处理,出现了超线程技术。超线程技术就是把处理器空闲的资源加以充分利用,把单个处理器逻辑出多核处理器的方式工作,在运行操作系统上会显示出两个或多个逻辑处理器让操作系统调用,于是操作系统就能将多个指令、线程交送到处理器来处理,这也就实现了对两个或多个线程的并行处理。

进入21世纪,出现了多线程处理器。英特尔公司、AMD公司等,早已把多线程技术引入服务器领域。目前硬件多线程已经成为主流应用,并越来越被视为处理器设计中实现最佳性能的有效方法。在一个支持超线程技术的平台上,一颗处理器具备的计算能力是两个线程,所以单颗处理器其实也已经具备了一个双线程的能力。最初,超线程的技术还是首先用在了服务器平台上,但最众所周知的具备超线程技术的处理器,就是曾经划时代的Inter奔腾Ⅳ产品线。在Inter奔腾Ⅳ大获成功之后,处理器又开始了一次更大的多核化变革,这次的变革不是发生在逻辑层次而是发生在物理层面上的变化。从Intel公司出品奔腾Ⅳ系列开始,双核心处理器逐渐崭露头角。此时的双核心处理器在物理意义上就是两个完整的处理核心封包在一个核心中,与双路系统的两颗完全分开的物理处理器不同,这两个完整的处理内核并存在一个处理器的内部,内核之间通讯不再需要通过处理器外部的总线来实现。当然,双核心的处理器也能够完成对两个指令的同时处理,也具备双线程的处理能力,也就是双核心、四线程的处理能力。

当今,多核心处理器已在市面上完全普及起来。不论是Intel还是AMD,在他们的产品列表上,单核心的处理器型号甚至双核处理器都已经基本不见踪迹,而与此同时,三核、四核、甚至六核都已经面世,而且价格低廉。AMD最新曝光的“推土机”系列双路32核心的平台性能卓越,同双核心处理器相同原理,四核心处理器就是把四个完整计算内核整合到一个处理器当中,每个核心都具有超线程的处理能力。而如果你是用四核心的处理器组成一个四路计算平台的话,4×2×4=32个线程的处理能力,在打开资源管理器的时候,看到32个线程同时工作的场景会是一件会令人向往的事情。

不过,也许你已经注意到,在整合多核心的一段时间内,超线程技术几乎发生里停滞不前的状况,在现在看多核心已经成了当今处理器发展的一个趋势,衡量处理器处理能力首先关注核心数量,因此市面上也随之出现了Inter、AMD的双核心、三核心、四核心甚至六核心的处理器,AMD速龙ⅡX4系列就是四核心四线程,处于成本考虑精简了三级缓存,AMD中高端系列羿龙ⅡX4系列增加6M三级缓存,性能提升较为明显,羿龙ⅡX6系列只是简单的由X4系列增加两个物理核心而已,都是单线程多核心性能提升不明显。所以AMD为了挽回高端处理器的颓势,推出了推土机架构的FX系列处理器,同样也有高中低的策略,低端的FX-4系列拥有4核心、4M二级缓存、8M三级缓存,中端FX-6系列拥有6核心、6M二级缓存、8M三级缓存,高端FX-8系列拥有8核心、6M二级缓存、8M三级缓存,性能的提升还是以核心的数量来决定,同时缓存的增加也起到部分作用。

与此同时,Intel却采用了截然不同的产品策略,也就是在发展核心数的同时,也在线程处理能力方面发力。本来超线程就是Intel的强项。于是,在新的处理器微架构Nehalem下,超线程技术又回来了,从低端的入门酷睿i3系列,双核心四线程处理器,性能直逼AMD羿龙ⅡX4系列四核四线程处理器,高端的最新的酷睿i7 990X、800这两个产品系列,酷睿800系列不仅仅物理上是四核心,而且同时支持更新一代的超线程技术,也就是说,同时具备了8线程的处理能力,酷睿i990X性能更加强劲六核心12线程,其性能完全打败AMD量产的高端系列。

多线程、超线程核心功耗:处理器发展阶段中一个无法避免的问题就是功耗。在单核心的年代,谈不上多核心、多线程技术,要想要提升处理器性能最简单的方法,就是超频,人为的提高处理器的频率,就需要提高电压使超频的处理器稳定的工作,处理器有个天生的缺陷就是漏电,电压升高漏电更加明显,处理器有相当一部分电流损耗在漏电产生的热量,这种热量、功耗对散热带来的压力已不容忽视。处理器的频率已经到达极限,频率越高、电压越高漏电损耗越明显。这个阶段多核心、多线成了解决处理器瓶颈的最佳技术。功耗是衡量处理器综合性能的主要指标之一,作为综合衡量线程、核心、功耗这三者关系的一个指标,由于物理核心的增加带来的功耗的提升,部分处理器达到130W的高功率,发热量不可避免的剧增。功耗的大幅度提升是多线程、多核心处理器发展新面临的技术问题。四核心处理器的功耗自然要比双核心的产品在满载的工作状态时要高上几十瓦。而超线程技术显然在目前阶段会更胜一筹,因为超线程技术是通过充分利用每个处理器内核资源来提高并行处理能力,所以对处理器并不产生额外的供电及散热,也就轻松减轻了功耗压力。从处理器利用率、功耗来看,支持超线程技术的多核处理器确实令人期待。

结论:随着对计算机处理速度的追求越来越高,通过增加多核心、多线程的技术已经成为当今处理器技术的主流。我认为处理器的发展功耗是最大的发展瓶颈,超线程技术在在不增加核心的数量能充分发挥处理器性能。超线程技术将成为今后处理器的发展过程中不可缺少的技术。

线程模型 篇3

对于Java语言编程机制而言,当它与同步线程模型进行了一定程度上的结合之后,能够对其系统并发执行程度进行较大幅度的提高,也就是说在原先的编程机制基础之上进一步的发展与提高。之前,在原先的Java编程机制之中存在着一定的弊端与局限性,这些问题的存在阻碍了Java编程机制的进一步发展,然而,自从在Java之中引入了同步线程模型之后,原先所存在的问题就能够得到一定程度上的控制。我们将同步线程模型与多进程模式进行一定程度上的对比分析,发现相比于多进程模式,前者能够发挥出更大的优越性,两者之间的差异性也能很明显的表现出来。两者之间的差异性主要表现在如下几个方面:对于进程而言,它的两个单位之间是相互分开的,而对于线程而言,它的每个执行单元具有一定程度的独立性,从客观的角度来说,实现独立调整已经不是问题,而是成为了一种非常熟练的工作。另一方面,通过技术上进步,各自分派也成为可能,每个进程都可以称之为独立出来的控制流,虽然在一定程度上具有了独立性,但从实质上来看,它并不是可独立拥有资源的基本单位。从这方面来看,在实际的操作当中,能够实现对独立拥有资源的单位频率进行切换,而且操作频率有所降低。进程并发的执行存在着两个十分必要的条件,这两个必要的条件分别害死可拥有资源的独立单位以及可独立调度及分派的基本单位。在传统的操作系统之中,这两项属性得到了切实体现。值得注意的是,总体进程之中,两种属性成为了一种非常坚实的基础,为进程的并发执行提供了强大的动力。还有就是,为了全面提高程序并发执行的有效性,有一部分的操作仍然只能够在系统中进行实时,这些操作主要有创建以及撤销等。对于一项资源的拥有者而言,在资源之中,对于不同的进程而言,它所具有的内存空间堆栈等也存在着一定程度上的差异。基于此,当系统在对这一系列的操作进行执行时,必须将这一情况纳入到考虑范围之内,腾出更大的空间。所以,在对系统进行设置时,对于进程数目的设定要求能够满足相应的标准,在对进程进行切换时也应该控制好次数。也就是说,从这一方面来看,这对进程并发更深层次的发展造成了一定程度上的阻碍作用。然而,对于Java同步线程模式而言,其难点主要集中在如何采取有效措施对并发程序设计中所出现的一系列问题进行合理而有效的解决。

一般情况下,在进行操作的过程之中,可以对线程进行一定程度的联想,将线程当做是程序当中的某个控制流程。如果是从固有系统角度出发,多数情况仅仅会存在和一个程度的控制流程,且这种控制流程具有相应的循环性,因此在原先的操作系统之后总,对于流程的执行职能按部就班,一步一步来完成,这种环境我们可以将之称作为单线程,无疑单线程的执行效率是相对较低的。近几年来,随着科学技术水平的不断进步,在原先的操作系统的基础之上引进了新技术,取得了较大程度上的进步,Java多线程模式便是其中一种典型技术。随着多线程模式的引入,原先的单线程模式也逐步淡出人们的视线。新型的Java多线程模式可以大幅提高执行效率,它不仅可以对程序的执行进行有效的支持,同时又能够对多个控制流程进行流畅操作。

一般情况下,在对Java多线程模式进行操作的过程之中,会出现两条控制线程,且这两条控制线程具有一定的特殊性,这两条控制线程存在于操作程序之中,且两者所执行的任务不同,一条线程的职责是对用户事件进行一定程度上的处理,而另一条线程作用是进行分析运算。值得注意的是,一般情况下处理器仅仅只有一个个时,也就是说线程是难以进行同时处理的,两条线程的执行之间必须存在着一定的空隙,如果其中一条线程正在等待对相关数据进行获取时,则系统就会自动进行线程的切换。正是由于这一原因,在对多线程模式进行引入之后,需要与计算机处理器个数组合。

2Java同步进程模型改进的尝试

对于Java编程语言而言,从整体角度上来看,线程模型正是其“软肋”,因为Java编程语言中的线程模型与实际情况的适应度较低,而且它所面对的对象相对狭窄,不能做到面面俱到。除此之外,Java语言的优势在于,其线程模型的满意度较高,能够实现很多的功能,减少了客观上的影响。从综合的角度来分析,Java作为一种编程语言,其线程模型仍然存在着诸多的缺陷,这些缺陷的存在阻碍了其进一步的发展。因此,采取有效措施对Java同步进程模型的改进十分重要。

而要想有效解决Java同步线程模型所存在的额缺陷,处理数据死锁是重中之重,就死锁本身来说,它的形成,主要是两个或者是两个以上的线程,互相之间的无限制等待情况,最终结果就是,两个线程都没有办法执行任务,那么这一状态就被称作为死锁状态。在Java同步线程之中,为了应对这一问题,通过对synchronized关键词进行一定程度上的使用,并在此基础之上实现对于对象的锁操作。当锁操作完成之后,之前所运用的synchronized关键词语句便能够得到有效的执行,除了以上的阐述之外,语句执行结束后,无论系统判定为正常也好、不正常也好,解锁操作都会自动完成。

一般情况下,对于并发线程而言,它们会对程序中的资源进行一定程度上的竞争,针对这一情况,必须采取措施对程序当中的共享资源进行有效而均衡的分配,从而达到线程在执行程序的过程之中能够对有限的资源进行充分利用的效果。在Java之中,并不存在专门为死锁提供检测的程序。所以,对于Java程序员而言,就必须认真处理死锁的相关程序。一般情况下,对于线程程序而言,很多都是可以对其进行归纳总结的,如果两个或两个以上的线程彼此之间无限期的等待,最终导致两个线程都无法有效的执行任务,从而造成死锁。比如如下程序:

从以上的编程来看,不难发现,一个对象会对应另一个与其存在一定关联的monitor对象,这个对象的具体作用可以用“守门人”来形容,每次仅仅允许一个synchronized方法进入,而如果其中的一个synchronized方法结束之后,monitor就会进行一定程度上的解锁,此时另外一个synchronized方法便可以开始执行。那么应该如何采取有效措施对这一死锁问题进行解决?其实只需要对正确线程程序进行编制,主要策略如下:首先,需要对给定的任务目标进行有效的完成,在执行目标的过程中,需要注意一定的同步性,即与其他任务同时进行,通过这样的操作,可以对每个线程完成的功能进行一定程度上的控制,并在此基础之上,就没有必要对两个线程进行使用,因为在这一操作之后,两者之间就存在着一定的依赖性,当从一个synchronized方法中调用另一个syn-chronized方法时,要谨慎方可。

3结束语

随着计算机技术的逐渐发展,Java语言编程的效果已经越来越明显,将其引入同步线程模型,可以充分改进其中的一些弊端,将大大提高运行效率。

摘要:目前状况下,随着经济的迅速发展以及科学技术水平的不断提高,我国的计算机科学技术取得了较大程度上的发展,为我国国民经济的提高做出重要贡献。众所周知,计算机科学技术的发展与语言编程机制的逐步发展与完善莫不可分。Java语言编程机制便是其中的重要一种,随着它的不断发展与改进,目前状况下它已经在计算机语言中有了十分广泛的运用。该文主要针对Java同步线程模型分析与改进进行研究与分析。

线程模型 篇4

关键词:分布式系统,构件模型,SMP,OpenMP,线程绑定

0 引 言

在松耦合的分布式系统中,应用对局部计算性能的需求不断提高(表现为对计算时延的敏感),因此将并行计算能力与分布式系统相结合是一个重要问题。所谓分布式系统是指一个系统的部件(包括计算机、传感器和执行机构等)处在不同的地理位置,但功能上合作的计算机系统[1]。从定义中可以看出,单纯的网络系统不是分布式系统,因为它没有任务上的分工合作机制。而任务的协同,要求有一定的时间约束。

在分布式系统中,当局部处理机执行的计算任务规模较大时,要求计算软件有较高的计算性能。目前,在一个处理器上集成多个计算内核比较普遍,称为对称多处理系统SMP(Symmetric Multi-Processor)。在SMP系统中,计算内核共享内存及数据总线,可以采用多线程并行计算获得更好的性能。

主要的操作系统都提供了多线程编程API,如Win32 Thread、Linux POXIS Thread等。但上述方法具有不可移植、编程繁琐的不足,限制了多线程技术的应用。而OpenMP技术具有增量并行的优点[2,3],即可以将一个串行程序代码逐步演化为并行程序代码。这使程序开发人员可以将并行程序编写划分为两个阶段:(1) 编写串行程序代码并调试;(2) 添加编译指导语句,逐步并行化原有程序并调试。这提高了并行程序的开发效率。

但是,已有的多线程模型没有考虑SMP系统中线程的无序迁移问题[3]。在操作系统的调度下,线程在多个内核之间频繁迁移会进行大量的数据移动操作,从而降低性能。本文针对该问题,在软件设计中提供了线程绑定的接口,能够改善多线程计算程序的执行效率。

相比于传统的分布式软件系统,本文将多线程并行计算技术与分布式松耦合环境相结合:SMP处理机的内部计算采用OpenMP多线程方式;网络上采用TCP/IP协议传输数据。并且,使用基于构件的软件工程方法CBSD(Component-based Software Development),提高了程序的可维护性和可重用性[4,5]。构件封装了程序的通信函数、配置逻辑和线程管理方法,从而提高系统对不同应用的适应性。通过构件组装,用户只需重新编写局部计算代码,缩短了开发时间。

1 OpenMP原理与线程绑定技术

OpenMP标准是一种SMP系统上的编程模型[2],它由编译指导、运行函数库和环境变量组成。这一规范简化了在SMP系统上创建多线程程序的开发过程。相比使用操作系统API来创建线程,OpenMP多线程有以下优点:

(1) 支持CPU内核数改变 当处理器升级到更多核后,已编译的OpenMP软件不需要重编译,就可以根据系统环境变量动态生成适当数量的线程。

(2) 编程模型易用性高 OpenMP通过编译指导语句就可将串行代码并行化,减少了程序员工作量。例如可以将无递归for循环自动分解为多线程。

(3)程序具有可移植性 使用OpenMP的多线程程序代码不需修改就可以在不同的操作系统上编译。

OpenMP的多线程程序的运行模型是:分叉与连接,在程序中局部的高计算负载代码段创建并行区。线程的分叉与连接是在并行区的首尾自动执行的,如图1所示。

首先,按照并发要素的不同,并行计算可以分为任务并行和数据并行,OpenMP支持这两种并发模型:

(1) 任务并行指一个程序可以分解为多个局部任务,且这些任务可以并发执行。在OpenMP中相应为并行分段模型:

在不同的section中,可以存在数据交换与协作执行,即线程同步与互斥关系。当使用barrier指导语句时,所有线程要在此处同步;而使用critical指导语句时,同时只能有一个线程进入[6]。

(2) 数据并行是指程序中所要计算的数据可以分解为多个数据块,并且这些数据块可以被独立地操作。在OpenMP中相应为并行循环(parallel for)模型:

这里需保证N阶for循环是非迭代的,即上一阶循环的结果不作为下一阶循环的输入。

其次,为了消除由于线程迁移而导致的性能降低,OpenMP线程绑定技术将线程固定在指定内核上运行。当前主要有两种方法实现线程绑定操作:环境变量方式和调用API方式。

(1) 主流编译器,如GCC,编译的OpenMP并行程序可以通过设置环境变量实现线程绑定。例如:

export GOMP_CPU_AFFINITY="1 3 5 7"

上述环境变量表示将四个线程绑定到CPU第1、3、5、7号内核上运行。

(2) 一些编译器,如Intel Compiler,支持使用线程绑定API,实现线程与内核的绑定。

在OpenMP程序中,可以使用kmp_set_affinity()函数来设置当前线程与内核core_id的绑定关系。本文使用这一方式,将在2.3节中详细说明。

2 构件模型的设计与实现

2.1 软件框架结构

本文提出的分布式构件模型是基于中间件设计的,中间件屏蔽了下层异构,并提供明确定义的接口,使分布式构件具有可移植性和简洁的内部结构。采用本构件实现的分布式系统具有如图2所示的分层结构。它包含三个主要部分:(1) 系统层,是基于网络的分布式的软硬件平台;(2)构件层,是可复用的分布式构件及其连接关系,实现数据处理逻辑;(3) 用户层,是完成用户数据的输入与显示的交互界面软件。

在构件层中,本文提出的组成分布式构件模型的三种基本构件为:计算单元(EU)、数据端口和连接子。在系统层中,OpenMP是并发计算中间件,为EU构件的设计提供了多线程并行计算能力。通信库的使用,使数据端口和连接子的设计与底层网络配置细节相隔离,如TCP、CORBA、COM等。

EU构件是最主要的基本构件,它负责计算任务的多线程执行与端口的管理等,以实现高性能的分布式计算。EU构件通过输入端口得到待处理数据,并通过输出端口将处理结果发送出去。端口为EU构件提供了输入输出缓冲区,并绑定对应具体网络的连接子。根据不同的分布式应用需求,可能需要在构件之间实现3种不同的通信方式:一对一、一对多或多对一。EU构件通过聚合多个端口,实现不同的通信拓扑结构。连接子封装了与网络硬件交互的通信代码,使通信与计算分离。这里的通信代码,可以使用TCP的socket API接口,或分布式中间件(如CORBA,DDS等)。

一个实际可部署的分布式构件的结构如图3所示,它由上述基本构件组合而成,称为复合构件。其中,EU构件包含多线程计算任务、线程管理和端口管理等功能属性。线程管理接口设定计算任务的线程数量,端口管理接口设定端口的添加与删除,并定义连接子间的连接关系。两个复合构件的输入连接子与输出连接子连接以实现数据传输,因此实际的分布式应用软件由多个复合构件组装而成。

一个复合构件的结构可以使用BNF范式进行描述,其中非终结符用斜体字加“< >”来表示,终结符用“[ ]”来表示:

<复合构件>::={<数据接口>, <计算单元>}

<数据接口>::={<输入端口集>, <输出端口集>}

<输入端口集>::={[输入端口1], [输入连接子1], …, [输入端口n], [输入连接子n]}

<输出端口集>::={[输出端口1], [输出连接子1], …, [输出端口n], [输出连接子n]}

<计算单元>::={[计算任务], [线程数]}

因此,基于本构件的分布式系统可以描述为:

<分布式系统>::={<构件实体集>, <构件关系集>}

<构件实体集>::={<复合构件1>, …, <复合构件n>}

<构件关系集>::={<构件连接1>, …, <构件连接n>}

<构件连接>::={[复合构件x的输出连接子a], [复合构件y的输入连接子b]}

2.2 分布式连接子的设计

通过构件化设计技术,将TCP通信代码封装,屏蔽了TCP的实现细节,具有很好的通用性。基于TCP的连接子与分布式中间件(如DDS、COM等)提供的复杂API相比,具有小巧易用的特点,并且避免了中间层故障而引起的系统崩溃。

连接子分为两类:输出连接子和输入连接子。这两类连接子与输出、输入端口相对应,并与相应端口直接交互。端口和连接子是连接关系。对应于TCP/IP协议,本文设计了相应的TCP连接子。

如图4所示,两类连接子connector_out类和connect_in类,都是connector类的实现并定义了数据收发的虚函数sendData()和recvData()。TCP连接子类继承于相应的父类,定义了私有的通信函数,并重写了父类的数据收发虚函数,实现了基于TCP的数据通信。

一对连接子在建立TCP连接时,使用一致的链路信息结构(link_info),其定义如下:

连接子实现TCP点对点通信的具体配置过程为:

(1) 数据发送方(conn_out_tcp)作为TCP通信中的服务端 首先绑定本地IP地址(my_info.ip_addr)及IP端口(my_info.ip_port)。之后等待连接请求,并建立TCP连接。

(2) 数据接收方(conn_in_tcp)作为TCP通信中的客户端 首先设定TCP连接目标的IP地址(my_info.ip_addr)即IP端口(my_info.ip_port)。然后发送连接请求,并返回套接字。

因此,建立通信要确定一致的链路信息,并告知一对连接子,其中ip_addr与发送方主机的IP地址一致。

2.3 OpenMP线程绑定的实现方法

在EU构件中,使用OpenMP实现线程到内核的绑定,以优化并行效率。在Intel Compiler中提供了对OpenMP标准的完整支持,并相应提供了一组线程绑定API。本文分析了有关API的实现方法,将其封装在计算构件(EUComp)类中。其具体代码为:

其中,函数参数me由kmp_create_affinity_mask(&me)初始化,用于储存当前线程设置的绑定信息。调用函数kmp_set_affinity_mask_proc(core, &me)可以建立指定的CPU内核core与变量me的对应关系。

bind()函数实现了SMP节点上的线程绑定方法,其调用位置在计算任务的OpenMP并行区中,由各个线程分别调用。在线程绑定实施之后,即可开始具体的计算工作。以并行for循环为例,其主要代码:

上述代码创建了4个计算线程,并将每个线程绑定到独立的内核运行。这一方法既充分利用了硬件资源、提高了并行度,又避免了线程无序迁移引起的性能下降。

3 应用实例与分析

在分布式系统的设计中,通常考虑处理机的计算性能和处理机间的网络通信时延。本实验设计了两个示例构件:(1) FFT构件,将512×1024个浮点FFT运算作为计算负载,对比了使用OpenMP线程绑定技术对计算性能的影响;(2) 数据源构件产生仿真数据,作为FFT构件的输入,以测试TCP连接子的数据传输性能。

实验的硬件平台是两台工作站通过千兆网络连接,Intel 4 cores 2.4GHz(支持HT超线程技术)、2G内存。软件平台为Linux 5.3 64bit和Intel Compiler 10.1及IPP函数库。其中,Intel IPP函数库提供了FFT计算函数。

在计算性能测试中,采用1、2、4、6、8个线程,分别进行有、无线程绑定的对比实验,共十组。每组实验执行5次取平均值。FFT构件中计算任务的主要代码如下:

从图5可以看出,在关闭线程绑定时,线程由1个增加到2个使计算并行度提高而计算时延减少。但当线程数继续增加时,由于线程的无序迁移和竞争资源的影响,导致计算时延显著增加。当线程数增加到8个时,计算时延达到了22ms(图中未标出)。这说明,在不使用线程绑定时,线程数的增加反而会使计算性能出现显著地下降。在开启线程绑定后,线程不迁移而使计算性能逐渐提高。也可以看出计算性能的提高与线程增加不呈线性关系,这是因为线程的管理开销也随之增大。当线程为8个时,线程管理开销过大而使性能略有下降。相比较而言,EU构件的线程绑定功能可以有效提高计算性能。

在网络性能测试中,两台处理机通过一个千兆交换机连接。使用四组不同大小的浮点数据块,通过TCP连接子在两台处理机间往返100次取平均值再除以2,测得单向通信时延,如图6所示。

可以看出随着数据块的增大,传输时延基本呈线性增加。通过计算,可以得出在不同的数据块大小时,网络的吞吐量基本为118.65MB/s,而千兆网络的理论峰值速率为125MB/s。可以看出,使用TCP连接子可以较好地利用网络带宽资源,能够满足分布式应用的需求。

4 结 语

分布式系统因其松耦合的特点而得到了广泛的应用,本文使用CBSD方法设计了一种支持OpenMP线程绑定的分布式构件模型。在分析了OpenMP线程绑定原理的基础上,该构件模型实现了线程绑定控制接口,提高了EU构件的计算性能。使分布式软件也可以满足一般的高性能计算需求,通过构件组装方法开发应用程序使程序具有较好的适应性。本文给出了使用TCP套接字的连接子的设计方案,以及EU构件中线程绑定接口的实现方案。通过实验验证,线程绑定能够有效提高多线程程序的执行效率,构件的数据传输能力也满足应用需求。

参考文献

[1]Hagit A,Jennifer W.分布式计算[M].2版.北京:电子工业出版社,2008.

[2]李建江,舒继武,陈永健,等.一种基于动态并行区的OpenMP程序开发模式[J].计算机研究与发展,2006.43(3):496-502.

[3]徐磊,徐莹,张丹丹.多核构架下OpenMP多线程应用运行性能的研究[J].计算机工程与科学,2009,31(11):50-53,57.

[4]王晨,周颖,张德富.一种并行分布对象的互操作模型[J].软件学报,1999,10(8):861-867.

[5]王静,吴健.一种基于分布式对象技术的分布式监控系统设计[J].计算机应用与软件,2007,24(9):83-84,90.

[6]武汉大学多核构架与编程技术课程组.多核构架与编程技术[M].武汉:武汉大学出版社,2010.

线程模型 篇5

Delphi和Matlab混合编程的常用方法有多种[1,2,3],其中基于COM组件技术的编程方法得到了广泛关注[4,5]。这种方法是利用Matlab提供的COM Builder工具将编写好的M文件编译生成DLL库,之后供Delphi调用,该方法可以脱离Matlab环境,因而极大的方便了应用程序的发布。但其也有缺点,其中之一是在应用程序中调用DLL库时容易出现用户界面冻结,使用户以为程序失去响应。为此,该文对此进行研究,提出一种基于COM STA线程模型的改进方法。

1 相关技术

1.1 COM组件

COM即组件对象模型(Componet Object Model,COM),是一种以组件为发布单元的对象模型,该模型使各应用程序组件可以用一种统一的方式进行通讯。

在COM标准中,COM对象被完美地封装起来,客户无法访问对象的实现细节,提供给用户的唯一访问途径是通过COM接口来实现。COM接口有两方面的含义:其一,它是一组可供调用的函数,客户可以让该对象做某些事情;其二,接口是组件及其客户程序之间的协议。使用COM编程实现了与编程语言无关的软件重用。

1.2 基于COM组件实现Delphi与Matlab混合编程

传统的基于COM组件实现的Delphi与Matlab混合编程[4,5],其第一步是编写M文件,之后在Matlab环境下输入命令comtool,启动Matlab COM Builder,设置好相关参数并添加好M文件后,就可以编译生成相应的COM组件,并将该COM组件注册到Windows的注册表中,最后在Delphi中直接调用该COM组件,实现混合编程。但这种方法基于的是单线程技术,所开发的应用程序在运行过程中容易出现界面冻结。原因分析如下:

假设有一个利用Matlab实现的COM服务器,Matrix Server,该服务器提供有一种方法Mul,功能是实现矩阵相乘运算。

假设该服务器同通常的应用程序一样,采用的是单线程技术。考虑3个客户(C1,C2,C3),每一客户都创建一个Matrix实例Matrix Server。这3个客户都同时调用IMatrix.Mul(假定第一个是C1,随后是C2,接着C3)。由于Matrix Server是单线程的,其只能按顺序一个一个执行命令,因此首先处理C1,然后C2,最后处理C3。

由于利用Matlab实现的大多是一些复杂的科学计算工作,其执行过程将花费较多的时间。假设执行完毕C1和C2各需要一分钟时间,那么最后完成C3工作将至少需要3分钟时间。这是因为C3必须等待C1和C2完成之后才能进行。这样就出现界面冻结。

1.3 COM线程模型

1.3.1 套间

“套间”是指存放一组对象的地方,该组对象共享相同的线程行为和要求。COM线程模型包括单线程套间STA、多线程套间MTA和中性套间(Neutral Apartment)。一个进程可以有多个STA,但最多只能有一个MTA。

1.3.2 单线程套间STA

STA是COM线程模型中一个重要的概念。在一个STA中,有且只有一个线程,驻留在该STA中的所有对象都服务于该线程。由于STA中仅有一个线程,因而被默认序列化,无需考虑多线程编程中的同步问题[7],如图1所示。

由于一个STA包含一个线程,多个STA包含多个线程,因而可以利用创建多个STA在一个服务中实现多线程。

假设在C1、C2、C3调用IMatrix.Mul时分别创建一个线程实例,这样C1、C2、C3将分别工作在服务线程T1、T2、T3上。这样C1、C2、C3三个客户可以同时调用IMatrix.Mul,而无需等待,从而可以消除界面冻结。图2所示为利用STA实现C1、C2、C3同时调用IMatrix.Mul。

在使用STA时需要注意的是,由于STA套间中所有的COM组件代码都运行于主STA(第一个调用Co Initialize函数的线程),如果主线程没有调用Co Initialize,那么第一个调用Co Initialize的工作线程就会成为主STA,而工作线程随时可能中止,这种情况下,一旦工作线程中止,主STA也就不复存在了,因此必须在主线程中调用Co Initialize初始化主STA。初始化STA也可以使用Co Initialize Ex(nil,COINIT_APARTMENTTHREADED)函数,它与Co Initialize(nil)等效。

通过查阅Windows注册表键,由Matlab生成的COM组件,其线程模型Threading Model=Both,说明其既支持STA,也支持MTA。

2 利用STA线程模型实现Delphi调用COM组件

Delphi调用COM组件之前需要利用Co Initialize(nil)函数初始化COM组件,以便让COM知道如何同调用线程工作,最后需要调用Co Un Initialize函数关闭COM,卸载在该线程中已装载DLL,释放线程中使用的资源,以及关闭线程中所有打开的RPC连接。

假设有一个利用Matlab生成的COM组件signal.dll,在Delphi XE中调用该组件的方法如下:

1)打开Delphi XE集成开发环境,新建一个包,设该包名命名为signal,编译并安装该包,然后关闭该包;

2)选择Component→Import Component→Import a Type Library,出现Import Component对话框后,找到signal.dll,点击下一步,在Palette Page中选择Active X,再点击下一步,选择“Install to a Existing Package”,继续下一步,定位到在第(1)步中安装的包signal.bpl,单击完成,即可将signal.dll安装到Delphi XE中;

3)在所建的Delphi XE工程中,新建一个Thread Object文件,类名为TSignal,在该文件的Execute过程中输入以下代码:

调用Form1中的Signal1组件;

在Form1中Button1的On Click事件中输入以下代码:

通过以上四个步骤,就可以实现Delphi与Matlab混合编程,而且在执行COM组件所提供函数的同时,还可以进行其它的操作,而不会出现界面冻结。

3 应用实例

下面用一个实际例子说明如何利用COM STA线程模型实现Delphi和Matlab混合编程。该实例实现的是利用S变换提取信号包络。首先编写Matlab函数如下:

按照文献[4]介绍的方法,制作并注册COM组件。设组件名为TSignal,按上文介绍的方法将TSignal安装到Delphi IDE中。

在Delphi新建一个工程,在窗体上添加如下控件:一个Signal,一个Chart,两个Edit,两个Label,一个Up Down,五个Button,一个Timer,一个Open Dialog,一个Al Wave Player,一个Al Audio Out,一个Sl Scope。设置Up Down控件的Associate属性为Edit1,Min=7000,Max=10000,设计的最终界面如图3所示。

新建一个Thread Object单元,单元名为Unit2,类名为TSt。在其Execute过程添加如下代码:

由于篇幅所限,其余代码省略。

在Delhi IDE中编译、连接工程,运行效果如图3所示。点击“分析”按钮进行分析的同时,还可以执行其他操作,而不会出现界面冻结现象。

4 结束语

本文详细讨论了基于COM STA线程模型的Delphi和Matlab混合编程的实现方法。按照这种方法进行应用系统的开发,不仅可以充分利用Matlab强大的科学计算功能和Delphi灵活的可视化设计功能,而且开发出的系统在运行过程中不会出现界面冻结现象,避免给用户程序僵死的假象。采用这种方法可以提高软件性能,为科学研究和工程计算提供更强的技术支持。该文所介绍的应用实例在、和环境下调试通过。

参考文献

[1]陈新.利用Delphi与Matlab进行科学计算的实现[J].四川兵工学报,2009,30(6):117-118.

[2]王艳丽.Delphi与Matlab混合编程的5种方法[J].菏泽学院学报,2006,28(2):100-102.

[3]蒋裕丰,何鲜峰,金永强.BP网络监测模型的Matlab&Delphi混合编程[J].水力发电,2008,34(1):88-91.

[4]姜银方,陈建希,李路娜.基于COM的Delphi和Matlab接口编程研究[J].计算机应用与软件,2008,25(2):31-34.

[5]吴小丽,丁维明,程力.Delphi动态调用Matlab COM组件实现二者混合编程[J].工业控制计算机,2011,24(3):1-3.

上一篇:运动状态下一篇:停车设施