📄 csdn_文档中心_win32 多线程的性能(1).htm
字号:
的后面,那么不得不说,您的进程非常失败。<BR><BR> 在本文中,我们这样定义一个计算的响应时间,计算完成所消耗的时间除以预计要消耗的时间。那么,如果一个应该消耗
10 毫秒(ms)的计算,而实际上消耗了 20 ms,那么它的响应处理周期就是 2,但是,如果就是同一个计算,却消耗了
200 ms (可能是因为有另一个长的计算与之竞争并优先)才结束,那么响应处理周期就是
20。显然,响应处理周期是越短越好。<BR><BR> 我们在后面将会看到,在将多线程引入一个应用程序中时,即使导致了整体性能的下降,容量仍然可能是一个有实际意义的因素;但是,要使容量成为一个有实际意义的因素,必需满足下面的一些条件:<BR><BR><BR><BR>每一个计算必需是相互独立的,只要计算一结束,任何计算的结果都可以被处理或使用。如果您是某个大学足球队的队员,而且您们每一个队员都在同一个超级市场买自己的旅行食品,那么您的商品是先被处理还是后被处理、您花费了多长的时间为两件商品结帐、以及您为此等待了多长的时间,这些都无关紧要,因为最后您的汽车是不会离开的,除非所有的队员都买完了食品。所不同的只是您的等待时间,要么是花费在排队等待结帐,要么是如果超级收银员已经为您服务,时间就花费在等待其他人上。<BR><BR> 这一点很重要,但却常被忽略。就象我前面所提到的,大多数的应用程序迟早都会显式或隐式地同步其计算。例如,如果您的应用程序从不同的文件并发地收集数据,您可能会想要在屏幕上显示结果,或者把它们保存到另一个文件中。在前面一种情况下(在屏幕上显示结果),您应该意识到,大多数图形系统都执行某种的内部批处理或串行操作,除非所有的输出数据都已收集到,否则是根本不会有好的显示的;在后面的情况下,(保存结果到另一个文件),除非整个原型文件已被写入完毕,一般不是您的应用程序(或其他的应用程序)所能完全处理的。所以,如果某个人或某些东西以某种形式将结果顺序化了,不管是应用程序、操作系统、甚至是用户,那么您在处理文件时所能得到的好处可能就会消失了。<BR><BR><BR>计算之间在量上必需有明显的差异。如果超级市场中的每一个顾客都只有两件商品需要结帐,则超级收银员方式一点优势都没有;如果他不得不在3个结算柜台之间跳来跳去,而每一个要被服务的顾客仅有2个(或3个、4个或n个)商品要结算,那么每一个顾客都不得不等待几倍的时间来完成他或她的结算,这比让所有的顾客在一起排队还要糟糕。在这里把多线程想象为shock吸收装置:短的计算并不会冒被排在长的计算之后的危险,但是它们被分成线程并且花费了更多的时间,而本来它们可以在更短的时间内完成。<BR><BR><BR>如果计算的长短可以事先决定,那么串行处理可能比多线程处理要好,您只要简单地以时间长短按升序排列计算就可以了。在超级市场的例子中,就相当于按照顾客的商品数来站排(Express Lane
方案的一种变种),这种想法是基于这样的考虑,只有很少的商品的顾客很喜欢它,因为他们不会为一点的事情而耽误很多的时间,
而那些有很多货物的顾客也不会在意,因为无论如何要完成其所有的结算都要花费很长的时间,而且在他们前面的每一个人的商品都比它少。<BR><BR> 如果只是大致知道计算时间的一个范围,但是您的应用程序不能排序这些计算,那么您应该花些时间做一次最坏情况的分析。在这样的分析中,您应该假定这些计算不是按照时间的升序顺序来排序的,相反,它们是按照时间的降序来排序的。从响应这个角度来讲,这中方案是最坏的情形,因为按照前面所定义的公式,每一个计算都将具有其最高可能的响应处理周期。<BR><BR><BR>快速响应(Responsiveness)<BR><BR><BR> 我将在这里讨论的、应用程序多线程化的最后一个准则是快速响应(在语言上与响应非常接近,足以使您迷惑不解)。在本文中,如果一个应用程序的设计是保证用户总是能够在一个很短的时间(很短的时间指时间非常短,使得用户感觉不到应用程序被挂起)内完成与应用程序的交互,那么我们就简单一点,定义该应用程序为响应快速的应用程序。<BR><BR> 对于一个带有
GUI 的 Win32
应用程序,快速响应可以被很简单地实现,只要确保长的计算被委托给后台线程,但是实现快速响应所要求的结构可能要求较高的技巧,正如我前面所提到的,某些人可能会等待某个计算在某个时间返回,所以在后台执行一个长的计算可能需要改变用户界面(例如,需要添加一个“取消”按钮,并且依赖该计算结果的菜单项也不得不变灰)。<BR><BR> 除了性能、容量和快速响应之外,其他的一些原因也可能影响多线程设计。例如,在某些结构下,必需让计算以一种伪随机方式(脑海中再次出现的例子是Bolzmann
机器类型的神经网络,在这种网络中,仅当该网络中的每一个节点异步执行其计算时,该互联网络的预期行为才能够工作)。但是,在本文中,我将把讨论的范围限制在上面所涉及的三个因素,那就是:性能、容量和快速响应。<BR><BR><BR>测试的实现<BR><BR><BR> 我曾经听说过许多关于抽象(abstraction)机制的讨论,说它封装了所有多线程的糟糕(nasty)方面到一个
C++
对象中,并且因此使一个应用程序获得了多线程的全部优点,而不是缺点。<BR><BR> 在本文中,我一开始就设计这样一个抽象。我将为一个
C++ 的类 ConcurrentExecution 定义一个原型,该类将含有成员函数例如:DoConcurrent 和
DoSerial,并且这两个成员函数的参数将是一个普通对象数组和一个回调函数数组,这些回调函数将被每一个对象并发或串行地调用。该
C++
类将封装所有关于保持该线程和内部数据结构的真实(gory)细节。<BR><BR> 但是,对我来说,从一开始我就十分清楚,这样的一个抽象的用处十分有限,因为在设计一个多线程应用程序时的最大量的工作成了一个无法自动完成的任务,这个工作就是决定如何实现多线程。ConcurrentExecution
的第一个限制是回调函数将不允许显式或隐式的共享数据;或回调函数需要任何其他形式的同步操作,而这些同步操作将立刻牺牲掉所有该抽象所带来的优点,并且打开所有“精彩”的同步世界中的陷阱和圈套,例如死锁、竞争冲突、或需要非常复杂的复合同步对象。<BR><BR> 同样,也不允许那些可能潜在地被并发执行的计算来调用
UI,因为就象我前面所讲到的,Win32 API 对于调用 UI 的线程强迫了许多个隐式的同步操作。请注意,还有许多其他的
API 子集和库对于共享它们的线程强迫了隐式的同步操作。<BR><BR> 这些的限制使
ConcurrentExecution
只具有极其有限的功能,说具体一点,就是一个管理纯粹工作者线程的抽象(完全独立的计算大多数情况下仅限于在非连续内存区域的数学计算)。<BR><BR> 然而,事实证明实现
ConcurrentExecution
类并且在性能测试中使用它是非常有用的,因为,当我实现了该类,并且设计和运行了该测试之时,许多关于多线程的隐藏起来的细节都暴露出来了。请清楚以下一点,虽然
ConcurrentExecution
类可以使多线程更容易处理,但是如果想要在商业产品中使用它,那么该类的实现还需要一些其他的工作。特别要提到的一点时,我忽略了所有的错误情况处理,这是不可忍受的。但是我假定只用于测试时(我明显地使用了
ConcurrentExecution),错误不会出现。<BR><BR><BR>ConcurrentExecution
类<BR><BR><BR>下面是 ConcurrentExecution 类的原型:<BR><BR><BR>class
ConcurrentExecution<BR><BR>{<BR><BR>< private members
omitted><BR><BR>public:<BR><BR>ConcurrentExecution(int
iMaxNumberOfThreads);<BR><BR>~ConcurrentExecution();<BR><BR>int
DoForAllObjects(int iNoOfObjects,long
*ObjectArray,<BR><BR>CONCURRENT_EXECUTION_ROUTINE
pObjectProcessor,<BR><BR>CONCURRENT_FINISHING_ROUTINE
pObjectTerminated);<BR><BR>BOOL DoSerial(int iNoOfObjects,
long *ObjectArray,<BR><BR>CONCURRENT_EXECUTION_ROUTINE
pObjectProcessor,<BR><BR>CONCURRENT_FINISHING_ROUTINE
pObjectTerminated);<BR><BR>};<BR><BR><BR>该类是从 Thrdlib.dll
库中导出的,而 Thrdlib.dll 库是示例测试套件 THRDPERF
中的一个工程。在讨论该类的内部结构之前,让我们首先讨论成员函数的语义(semantics):<BR><BR><BR>ConcurrentExecution::ConcurrentExecution(int
iMaxNumberOfThreads)<BR><BR>{<BR><BR>m_iMaxArraySize =
min(iMaxNumberOfThreads,
MAXIMUM_WAIT_OBJECTS);<BR><BR>m_hThreadArray = (HANDLE
*)VirtualAlloc(NULL,m_iMaxArraySize*sizeof(HANDLE),<BR><BR>MEM_COMMIT,PAGE_READWRITE);<BR><BR>m_hObjectArray
= (DWORD
*)VirtualAlloc(NULL,m_iMaxArraySize*sizeof(DWORD),<BR><BR>MEM_COMMIT,PAGE_READWRITE);<BR><BR>//
当然,一个真正的实现必需在这里提供对错误的处理...<BR><BR>};<BR><BR><BR>您可能会注意到构造函数
ConcurrentExecution 有一个数字参数。该参数指定了该类的实例所支持的“并发的最大度数”;换句话说,如果某个
ConcurrentExecution 的实例被创建时,n 是它的一个参数,那么在任何给定的时间不能有超过 n
个计算在执行。根据我们以前的分析,该参数就意味“无论有多少个顾客在等待,打开的结算柜台数不要多于 n
个”。<BR><BR><BR>int DoForAllObjects(int iNoOfObjects,long
*ObjectArray,<BR><BR>CONCURRENT_EXECUTION_ROUTINE
pObjectProcessor,<BR><BR>CONCURRENT_FINISHING_ROUTINE
pObjectTerminated);<BR><BR><BR> 这是在这里被实现的唯一有趣的成员函数。DoForAllObjects
的主要参数是一个对象的数组、一个处理器函数、和一个终结器函数。关于对象完全没有强制的格式;每次该处理器被调用时,将有一个对象被传递给它,而且完全由该处理器来解释对象。第一个参数
iNoOfObjects,仅仅是要 ConcurrentExecution 知道在对象数组中的元素数。请注意,在调用
DoForAllObjects 时,如果对象数组的长度为 1,那么它与调用 CreateThread
就非常相似(有一点不同,那就是 CreateThread
不接受一个终结器参数)。<BR><BR> DoForAllObjects
的语义如下:处理器将为每一个对象而调用。对象被处理的顺序并未指定;所有能够担保的只是每一个对象都将在某个时间被传递给处理器。并发的最大度数是由传递给
ConcurrentExecution
对象的构造函数的参数来决定的。<BR><BR> 处理器函数不能访问共享的数据,并且不能调用到 UI
或做任何其他需要显式或隐式地串行操作的事情。目前,仅存在一个处理器函数能够对所有的对象工作;但是,要使用处理器数组来替代该处理器参数将是简单的。<BR><BR><BR>该处理器的原型如下:<BR><BR><BR>typedef
DWORD (WINAPI *CONCURRENT_EXECUTION_ROUTINE)<BR><BR>(LPVOID
lpParameterBlock);<BR><BR><BR> 当该处理器已经完成了在一个对象上的工作之后,终结器函数将立即被调用。与处理器不同,终结器函数是在该调用函数的环境中被串行调用的,并且可以调用所有的例程和访问调用程序所能够访问的所有数据。但是,应该要注意的是,终结器应该被尽可能地优化,因为终结器中的长计算会影响
DoForAllObjects
的性能。请注意,尽管只要处理器结束了每一个对象终结器就会立即被调用,直到最后一个对象已经被终结之前,DoForAllObjects
本身并没有返回。<BR><BR> 我们为什么要经历这么多使用终结器的痛苦?我们同样可以让每一个计算在处理器函数的最终结束时执行终结器代码,是吗?<BR><BR>这样基本上是可以的;但是,有必要强调终结器是在调用
DoForAllObjects的线程环境中被调用的。这样的设计使在每一个计算进入时处理它们的结果更加容易,而无须担心同步问题。<BR><BR><BR>终结器函数的原型如下:<BR><BR><BR>typedef
DWORD (WINAPI *CONCURRENT_FINISHING_ROUTINE)<BR><BR>(LPVOID
lpParameterBlock,LPVOID
lpResultCode);<BR><BR><BR> 第一个参数是被处理的对象,第二个参数是处理器函数在该对象上的结果。<BR><BR> DoForAllObjects
的同类是 DoSerial,DoSerial 与 DoForAllObjects
具有相同的参数列表,但是计算是被以串行的顺序处理的,并且以列表中的第一个对象开始。<BR></TD></TR></TBODY></TABLE></DIV>
<P> </P><BR><BR></TD></TR></TBODY></TABLE></TD></TR></TBODY></TABLE><BR>
<TABLE align=center bgColor=#006699 border=0 cellPadding=0 cellSpacing=0
width=770>
<TBODY>
<TR bgColor=#006699>
<TD align=middle bgColor=#006699 id=white><FONT
color=#ffffff>对该文的评论</FONT></TD>
<TD align=middle>
<SCRIPT src="CSDN_文档中心_Win32 多线程的性能(1).files/readnum.htm"></SCRIPT>
</TD></TR></TBODY></TABLE><BR>
<DIV align=center>
<TABLE align=center bgColor=#cccccc border=0 cellPadding=2 cellSpacing=1
width=770>
<TBODY>
<TR>
<TH bgColor=#006699 id=white><FONT
color=#ffffff>我要评论</FONT></TH></TR></TBODY></TABLE></DIV>
<DIV align=center>
<TABLE border=0 width=770>
<TBODY>
<TR>
<TD>你没有登陆,无法发表评论。 请先<A
href="http://www.csdn.net/member/login.asp?from=/Develop/read_article.asp?id=2840">登陆</A>
<A
href="http://www.csdn.net/expert/zc.asp">我要注册</A><BR></TD></TR></TBODY></TABLE></DIV><BR>
<HR noShade SIZE=1 width=770>
<TABLE border=0 cellPadding=0 cellSpacing=0 width=500>
<TBODY>
<TR align=middle>
<TD height=10 vAlign=bottom><A
href="http://www.csdn.net/intro/intro.asp?id=2">网站简介</A> - <A
href="http://www.csdn.net/intro/intro.asp?id=5">广告服务</A> - <A
href="http://www.csdn.net/map/map.shtm">网站地图</A> - <A
href="http://www.csdn.net/help/help.asp">帮助信息</A> - <A
href="http://www.csdn.net/intro/intro.asp?id=2">联系方式</A> - <A
href="http://www.csdn.net/english">English</A> </TD>
<TD align=middle rowSpan=3><A
href="http://www.hd315.gov.cn/beian/view.asp?bianhao=010202001032100010"><IMG
border=0 height=48 src="CSDN_文档中心_Win32 多线程的性能(1).files/biaoshi.gif"
width=40></A></TD></TR>
<TR align=middle>
<TD vAlign=top>百联美达美公司 版权所有 京ICP证020026号</TD></TR>
<TR align=middle>
<TD vAlign=top><FONT face=Verdana>Copyright © CSDN.net, Inc. All rights
reserved</FONT></TD></TR>
<TR>
<TD height=15></TD>
<TD></TD></TR></TBODY></TABLE></DIV>
<DIV></DIV><!--内容结束//--><!--结束//--></BODY></HTML>
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -