⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 csdn_文档中心_win32 多线程的性能(2).htm

📁 csdn10年中间经典帖子
💻 HTM
📖 第 1 页 / 共 3 页
字号:
    </TD>
    <TD align=middle width=620>
      <TABLE bgColor=#eeeeee border=0 cellPadding=0 cellSpacing=0 width=600>
        <TBODY>
        <TR bgColor=#ffffff>
          <TD align=middle height=10 width=50></TD>
          <TD align=right><A href="http://www.csdn.net/">CSDN</A> - <A 
            href="http://www.csdn.net/develop/">文档中心</A> - <FONT 
            color=#003399>Visual C++</FONT>&nbsp;&nbsp;&nbsp;&nbsp; </TD></TR>
        <TR>
          <TD align=middle height=5></TD>
          <TD align=middle width=500></TD></TR>
        <TR>
          <TD align=middle bgColor=#003399 height=10><FONT 
            color=#ffffff>标题</FONT></TD>
          <TD><B>&nbsp;&nbsp;&nbsp;&nbsp;Win32 
            多线程的性能(2)</B>&nbsp;&nbsp;&nbsp;&nbsp;vcbear(收藏) </TD></TR>
        <TR>
          <TD align=middle height=5></TD>
          <TD align=middle width=500></TD></TR>
        <TR>
          <TD align=middle bgColor=#003399><FONT color=#ffffff>关键字</FONT></TD>
          <TD width=500>&nbsp;&nbsp;&nbsp;&nbsp;Win32 多线程的性能(2)</TD></TR>
        <TR>
          <TD align=middle height=5></TD>
          <TD align=middle width=500></TD></TR></TBODY></TABLE><!--文章说明信息结束//-->
      <TABLE border=0 width=600>
        <TBODY>
        <TR>
          <TD align=left><BR>
            <P><FONT color=#ff8000>Win32 多线程的性能(2)</FONT></P>
            <TABLE border=0 width="100%">
              <TBODY>
              <TR>
                <TD bgColor=#98a2bc>作者:Microsoft公司供稿</TD></TR></TBODY></TABLE>
            <DIV align=left>
            <TABLE border=0 width="100%">
              <TBODY>
              <TR>
                <TD bgColor=#ebebeb>Ruediger R. Asche<BR>Microsoft Developer 
                  Network 技术小组<BR>ConcurrentExecution 
                  的内部工作<BR><BR><BR>  请注意:本节的讨论是非常技术性的,所以假设您理解很多有关 Win32 线程 API 
                  的知识。如果您对如何使用 ConcurrentExecution 类来收集测试数据更加感兴趣,而不是对 
                  ConcurrentExecution::DoForAllObjects 
                  是如何被实现的感兴趣,那么您现在就可以跳到下面的“使用 ConcurrentExecution 
                  来试验线程性能”一节。<BR><BR>  让我们从 DoSerial 
                  开始,因为它很大程度上是一个“不费脑筋的家伙”:<BR><BR><BR>BOOL 
                  ConcurrentExecution::DoSerial(int iNoOfObjects,long 
                  *ObjectArray,<BR><BR>CONCURRENT_EXECUTION_ROUTINE 
                  pProcessor,<BR><BR>CONCURRENT_FINISHING_ROUTINE 
                  pTerminator)<BR><BR>{ <BR><BR>for (int 
                  iLoop=0;iLoop&lt;iNoOfObjects;iLoop++)<BR><BR>{<BR><BR>pTerminator((LPVOID)ObjectArray[iLoop],(LPVOID)pProcessor((LPVOID)ObjectArray[iLoop]));<BR><BR>};<BR><BR>return 
                  TRUE;<BR><BR><BR>};<BR><BR><BR>  这段代码只是循环遍历该数组,在每一次迭代中调用处理器,然后在处理器和对象本身的结果上调用终结器。干得既干净又漂亮,不是吗?<BR><BR>  令人感兴趣的成员函数是 
                  DoForAllObjects。乍一看,DoForAllObjects 
                  所要做的也没有什么特别的——请求操作系统创建为每一个计算一个线程,并且确保终结器函数能够被正确地调用。但是,有两个问题使得 
                  DoForAllObjects 
                  比它的表面现象要复杂:第一,当计算的数目多于可用的线程数时,ConcurrentExecution 
                  的一个实例所创建的“并发的最大度数”参数可能需要一些附加的记录(bookkeeping)。第二,每一个计算的终结器函数都是在调用 
                  DoForAllObjects 
                  的线程的上下文中被调用的,而不是在该计算运行所处的线程上下文中被调用的;并且,终结器是在处理器结束之后立刻被调用的。要处理这些问题还是需要很多技巧的。<BR><BR>  让我们深入到代码中,看看究竟是怎么样的。该段代码是从文件 
                  Thrdlib.cpp 中继承来的,但是为了清除起见,已经被精简了:<BR><BR>int 
                  ConcurrentExecution::DoForAllObjects(int iNoOfObjects,long 
                  *ObjectArray,<BR><BR>CONCURRENT_EXECUTION_ROUTINE 
                  pObjectProcessor,<BR><BR>CONCURRENT_FINISHING_ROUTINE<BR><BR>pObjectTerminated)<BR><BR>{<BR><BR>int 
                  iLoop,iEndLoop;<BR><BR>DWORD iThread;<BR><BR>DWORD 
                  iArrayIndex;<BR><BR>DWORD dwReturnCode;<BR><BR>DWORD 
                  iCurrentArrayLength=0;<BR><BR>BOOL 
                  bWeFreedSomething;<BR><BR>char 
                  szBuf[70];<BR><BR><BR>m_iCurrentNumberOfThreads=iNoOfObjects;<BR><BR><BR>HANDLE 
                  *hPnt=(HANDLE 
                  *)VirtualAlloc(NULL,m_iCurrentNumberOfThreads*sizeof(HANDLE)<BR><BR>,MEM_COMMIT,PAGE_READWRITE);<BR><BR>for(iLoop=0;iLoop&lt;m_iCurrentNumberOfThreads;iLoop++)<BR><BR>hPnt[iLoop] 
                  = 
                  CreateThread(NULL,0,pObjectProcessor,(LPVOID)ObjectArray[iLoop],<BR><BR>CREATE_SUSPENDED,(LPDWORD)&amp;iThread); 
                  <BR><BR><BR>  首先,我们为每一个对象创建单独的线程。因为我们使用 CREATE_SUSPENDED 
                  来创建该线程,所以还没有线程被启动。另一种方法是在需要时创建每一个线程。我决定不使用这种替代的策略,因为我发现当在一个同时运行了多个线程的应用程序中调用时, 
                  CreateThread 
                  调用是非常浪费的;这样,同在运行时创建每一个线程相比,在此时创建线程的开销将更加容易接受,<BR><BR><BR>for 
                  (iLoop = 0; iLoop &lt; m_iCurrentNumberOfThreads; 
                  iLoop++)<BR><BR>{<BR><BR>HANDLE 
                  hNewThread;<BR><BR>bWeFreedSomething=FALSE;<BR><BR>// 
                  如果数组为空,分配一个 slot 和 boogie。<BR><BR>if 
                  (!iCurrentArrayLength)<BR><BR>{<BR><BR>iArrayIndex = 
                  0;<BR><BR>iCurrentArrayLength=1;<BR><BR>}<BR><BR>else<BR><BR>{<BR><BR>// 
                  首先,检查我们是否可以重复使用任何的 slot。我们希望在查找一个新的 slot 之前首先// 
                  做这项工作,这样我们就可以立刻调用该就线程的终结器...<BR><BR>iArrayIndex=WaitForMultipleObjects(iCurrentArrayLength,<BR><BR>m_hThreadArray,FALSE,0);<BR><BR>if 
                  (iArrayIndex==WAIT_TIMEOUT) // no slot 
                  free...<BR><BR>{<BR><BR>{<BR><BR>if (iCurrentArrayLength &gt;= 
                  m_iMaxArraySize)<BR><BR>{<BR><BR>iArrayIndex= 
                  WaitForMultipleObjects(iCurrentArrayLength,<BR><BR>m_hThreadArray,FALSE,INFINITE);<BR><BR>bWeFreedSomething=TRUE;<BR><BR>}<BR><BR>else 
                  // 我们可以释放某处的一个 
                  slot,现在就这么做...<BR><BR>{<BR><BR>iCurrentArrayLength++;<BR><BR>iArrayIndex=iCurrentArrayLength-1;<BR><BR>}; 
                  // Else iArrayIndex points to a thread that has been 
                  nuked<BR><BR>};<BR><BR>}<BR><BR>else bWeFreedSomething = 
                  TRUE;<BR><BR>}; // 在这里,iArrayIndex 
                  包含一个有效的索引以存储新的线程。<BR><BR>hNewThread = 
                  hPnt[iLoop];<BR><BR>ResumeThread(hNewThread);<BR><BR>if 
                  (bWeFreedSomething)<BR><BR>{ 
                  <BR><BR>GetExitCodeThread(m_hThreadArray[iArrayIndex],&amp;dwReturnCode); 
                  //错误<BR><BR>CloseHandle(m_hThreadArray[iArrayIndex]);<BR><BR>pObjectTerminated((void 
                  *)m_hObjectArray[iArrayIndex],(void 
                  *)dwReturnCode);<BR><BR>};<BR><BR>m_hThreadArray[iArrayIndex] 
                  = hNewThread;<BR><BR>m_hObjectArray[iArrayIndex] = 
                  ObjectArray[iLoop];<BR><BR>}; // 
                  循环结束<BR><BR><BR>  DoForAllObjects 的核心是 hPnt,它是一个对象数组,这些对象是当 
                  ConcurrentExecution 
                  对象被构造时分配的。该数组能够容纳最大数目的线程,此最大数目与在构造函数中指定的最大并发度数相对应;因此,该数组中的每一个元素都是一个"slot",并有一个计算居于之中。<BR><BR>  关于决定如何填充和释放的 
                  slots 算法如下:该对象数组是从头到尾遍历的,并且对于每一个对象,我们都做如下的事情:如果尚未有 slot 
                  已经被填充,我们使用当前的对象来填充该数组中的第一个 slot,并且继续执行将要处理当前对象的线程。如果至少有一个 slot 
                  被使用,我们使用 WaitForMultipleObjects 
                  函数来决定是否有正在运行的任何计算已经结束;如果是,我们在该对象上调用终结器,并且为新对象“重用”该 
                  slot。请注意,我们也可以首先填充每一个空闲的 slot,直到没有剩余的 slots 为止,然后开始填充空的 
                  slot。但是,如果我们这样做了,那么空出 slot 的终结器函数将不会被调用,直到所有的 slot 
                  都已经被填充,这样就违反了我们有关当处理器结束一个对象时,终结器立刻被调用的要求。<BR><BR>  最后,还有没有空闲 
                  slot 的情况(就是说,当前激活的线程数等于 ConcurrentExecution 
                  对象所允许的最大并发度数)。在这种情况下,WaitForMultipleObjects 将被再次调用以使得 
                  DoForAllObjects 处于“睡眠”状态,直到有一个 slot 空出;只要这种情况一发生,终结器就被在空出 slot 
                  的对象上调用,并且工作于当前对象的线程被继续执行。<BR><BR>  终于,所有的计算要么都已经结束,要么将占有对象数组中的 
                  slot。下列的代码将会处理所有剩余的线程:<BR><BR><BR>iEndLoop = 
                  iCurrentArrayLength;<BR><BR>for 
                  (iLoop=iEndLoop;iLoop&gt;0;iLoop--)<BR><BR>{<BR><BR>iArrayIndex=WaitForMultipleObjects(iLoop, 
                  m_hThreadArray,FALSE,INFINITE);<BR><BR>if 
                  (iArrayIndex==WAIT_FAILED)<BR><BR>{<BR><BR>GetLastError();<BR><BR>_asm 
                  int 3; // 
                  这里要做一些聪明的事...<BR><BR>};<BR><BR>GetExitCodeThread(m_hThreadArray[iArrayIndex],&amp;dwReturnCode); 
                  // 错误?<BR><BR>if 
                  (!CloseHandle(m_hThreadArray[iArrayIndex]))<BR><BR>MessageBox(GetFocus(),"Can't 
                  delete thread!","",MB_OK); // 
                  使它更好...<BR><BR><BR>pObjectTerminated((void 
                  *)m_hObjectArray[iArrayIndex],<BR><BR>(void 
                  *)dwReturnCode);<BR><BR>if (iArrayIndex==iLoop-1) continue; // 
                  这里很好,没有需要向后填充<BR><BR>m_hThreadArray[iArrayIndex]=m_hThreadArray[iLoop-1];<BR><BR>m_hObjectArray[iArrayIndex]=m_hObjectArray[iLoop-1];<BR><BR>};<BR><BR><BR>最后,清除:<BR><BR><BR>if 
                  (hPnt) 
                  VirtualFree(hPnt,m_iCurrentNumberOfThreads*sizeof(HANDLE),<BR><BR>MEM_DECOMMIT);<BR><BR><BR>return 
                  iCurrentArrayLength;<BR><BR><BR>};<BR><BR><BR>使用 
                  ConcurrentExecution 来试验线程性能<BR><BR><BR>  性能测试的范围如下:测试应用程序 
                  Threadlibtest.exe 的用户可以指定是否测试基于 CPU 的或基于 I/O 
                  的计算、执行多少个计算、计算的时间有多长、计算是如何排序的(为了测试最糟的情况与随机延迟),以及计算是被并发执行还是串行执行。<BR><BR>  为了消除意外的结果,每一个测试可以被执行十次,然后将十次的结果拿来平均,以产生一个更加可信的结果。<BR><BR>  通过选择菜单选项 
                  "Run entire test set",用户可以请求运行所有测试变量的变形。在测试中使用的计算长度在基础值 10 和 
                  3,500 ms 之间变动(我一会儿将讨论这一问题),计算的数目在 2 和 20 之间变化。如果在运行该测试的计算机上安装了 
                  Microsoft Excel,Threadlibtest.exe 将会把结果转储在一个 Microsoft Excel 
                  工作表,该工作表位于 C:\Temp\Values.xls。在任何情况下结果值也将会被保存到一个纯文本文件中,该文件位于 
                  C:\Temp\Results.fil。请注意,我对于协议文件的位置使用了硬编码的方式,纯粹是懒惰行为;如果您需要在您的计算机上重新生成测试结果,并且需要指定一个不同的位置,那么只需要重新编译生成该工程,改变文件 
                  Threadlibtestview.cpp 的开头部分的 TEXTFILELOC 和 SHEETFILELOC 
                  标识符的值即可。<BR><BR>  请牢记,运行整个的测试程序将总是以最糟的情况来排序计算(就是说,执行的顺序是串行的,最长的计算将被首先执行,其后跟随着第二长的计算,然后以次类推)。这种方案牺牲了串行执行的灵活性,因为并发执行的响应时间在一个非最糟的方案下也没有改变,而该串行执行的响应时间是有可能提高的。<BR><BR>  正如我前面所提到的,在一个实际的方案中,您应该分析每一个计算的时间是否是可以预测的。<BR><BR>  使用 
                  ConcurrentExecution 类来收集性能数据的代码位于 Threadlibtestview.cpp 
                  中。示例应用程序本身 (Threadlibtest.exe) 是一个真正的单文档界面 (SDI) 的 MFC 
                  应用程序。所有与示例有关的代码都驻留在 view 类的实现 CThreadLibTestView 中,它是从 
                  CEasyOutputView 继承而来的。(有关对该类的讨论,请参考"Windows NT Security in 
                  Theory and 
                  Practice"。)这里并不包含该类中所有的有趣代码,所包含的大部分是其数字统计部分和用户界面处理部分。执行测试中的 
                  "meat" 在 CThreadLibTestView::ExecuteTest 中,将执行一个测试运行周期。下面是有关 
                  CThreadLibTestView::ExecuteTest 的简略代码:<BR><BR><BR>void 
                  CThreadlibtestView::ExecuteTest()<BR><BR>{<BR><BR>ConcurrentExecution 
                  *ce;<BR><BR>bCPUBound=((m_iCompType&amp;CT_IOBOUND)==0); // 
                  全局...<BR><BR>ce = new ConcurrentExecution(25);<BR><BR>if 
                  (!QueryPerformanceCounter(&amp;m_liOldVal)) return; // 获得当前时间。 
                  <BR><BR>if (!m_iCompType&amp;CT_IOBOUND) 
                  timeBeginPeriod(1);<BR><BR>if 
                  (m_iCompType&amp;CT_CONCURRENT)<BR><BR>m_iThreadsUsed=ce-&gt;DoForAllObjects(m_iNumberOfThreads,<BR><BR>(long 
                  *)m_iNumbers,<BR><BR>(CONCURRENT_EXECUTION_ROUTINE)pProcessor,<BR><BR>(CONCURRENT_FINISHING_ROUTINE)pTerminator);<BR><BR>else<BR><BR>ce-&gt;DoSerial(m_iNumberOfThreads,<BR><BR>(long 
                  *)m_iNumbers,<BR><BR>(CONCURRENT_EXECUTION_ROUTINE)pProcessor,<BR><BR>(CONCURRENT_FINISHING_ROUTINE)pTerminator);<BR><BR>if 
                  (!m_iCompType&amp;CT_IOBOUND) 
                  timeEndPeriod(1);<BR><BR>delete(ce);<BR><BR>&lt; 
                  其他的代码在一个数组中排序结果,以供 Excel 
                  处理...&gt;<BR><BR>}<BR><BR><BR>  该段代码首先创建一个 ConcurrentExecution 
                  类的对象,然后,取样当前时间,(用于统计计算所消耗的时间和响应时间),并且,根据所请求的是串行执行还是并发执行,分别调用 
                  ConcurrentExecution 对象 DoSerial 或 DoForAllObjects 
                  成员。请注意,对于当前的执行我请求最大并发度数为 25;如果您想要运行有多于 25 

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -