乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      線程函數(shù)的設(shè)計(jì)以及線程同步要點(diǎn)(MsgWaitForMultipleObjects等)

       寂靜如河 2012-02-22

      使用多線程技術(shù)可以顯著地提高程序性能,本文就講講在程序中如何使用工作線程,以及工作線程與主線程通訊的問題。

      一 創(chuàng)建線程


          使用MFC提供的全局函數(shù)AfxBeginThread()即可創(chuàng)建一個(gè)工作線程。線程函數(shù)的標(biāo)準(zhǔn)形式為 UINT MyFunProc(LPVOID );此函數(shù)既可以是全局函數(shù),也可以是類的靜態(tài)成員函數(shù)。 之所以必須是靜態(tài)成員函數(shù),是由于類的非靜態(tài)成員函數(shù),編譯器在編譯時(shí)會自動(dòng)加上一個(gè)this指針參數(shù),如果將函數(shù)設(shè)置為靜態(tài)的成員函數(shù),則可以消除 this指針參數(shù)。如果想在線程函數(shù)中任意調(diào)用類的成員變量(此處指的是數(shù)據(jù)成員,而不是控件關(guān)聯(lián)的成員變量),則可以將類的指針作為參數(shù)傳遞給線程函數(shù),然后經(jīng)由該指針,就可以調(diào)用類的成員變量了。

      //線程函數(shù),類的靜態(tài)成員函數(shù)
      UINT CThreadTest::TH_SetProgress(LPVOID lpVoid)
      {
             CThreadTest *pTest=(CThreadTest *)lpVoid;
             pTest->SetProgress();
             return 0;
      }

      //類的成員函數(shù),此函數(shù)執(zhí)行實(shí)際的線程函數(shù)操作,卻可以自如的調(diào)用成員數(shù)據(jù)
      void CThreadTest::SetProgress()
      {
             int nCount=0;
             while (1)
             {
                    m_progress.SetPos(nCount); //設(shè)置進(jìn)度條進(jìn)度
      //            this->SendMessage(WM_SETPROGRESSPOS,nCount,0);//也可以采用這種方式設(shè)置
                    nCount++;
                    if (g_exitThread)
                    {
                           return;
                    }
                    Sleep(200);
             }
      }

      二 線程函數(shù)體的設(shè)計(jì)

          有過多線程設(shè)計(jì)經(jīng)驗(yàn)的人都有體會,多線程設(shè)計(jì)最重要的就是要處理好線程間的同步和通訊問題。如解決不好這個(gè)問題,會給程序帶來潛藏的隱患。線程的同步可以利用臨界區(qū)、事件、互斥體和信號量來實(shí)現(xiàn),線程間的通訊可利用全局變量和發(fā)消息的形式實(shí)現(xiàn)。其中事件和臨界區(qū)是使用得比較多的工具。

      請看下面的線程函數(shù)體:

      UINT AnalyseProc(LPVOID   lVOID)
      {
             if(WAIT_OBJECT_0== WaitForSingleObject(m_eventStartAnalyse.m_hThread,INFINITE))
             {
                    while (WAIT_OBJECT_0 == WaitForSingleObject(m_eventExitAnalyse.m_hThread,0))
                    {
                           DWORD dRet=WaitForSingleObject(m_eventPause.m_hThread,0);
                           if (dRet == WAIT_OBJECT_0)
                           {
                                  //暫停分析
                                  Sleep(10);
                           }
                           else if (dRet == WAIT_TIMEOUT)
                           {
                                  //繼續(xù)分析
                                  //
                           }
                    }
             }
             return 0;
      }

          上面的線程函數(shù)用到了三個(gè)事件變量eventStartAnalyse、eventExitAnalyse和eventPause,分別用來控制線程函數(shù)的啟動(dòng)、退出以及暫停。再配以WaitForSingleObject函數(shù),就可以自如的控制線程函數(shù)的執(zhí)行,這是在線程函數(shù)體內(nèi)應(yīng)用事件變量的典型方式 ,也是推薦的方式。

          無論是工作線程還是用戶界面線程,都有消息隊(duì)列,都可以接收別的線程發(fā)過來的消息也可以給別的線程發(fā)送消息。給工作線程發(fā)消息使用的函數(shù)是PostThreadMessage() 。此函數(shù)的第一個(gè)參數(shù)是接收消息的線程的ID。此函數(shù)是異步執(zhí)行的,機(jī)制和PostMessage一樣,就是把消息拋出后就立即返回,不理會消息是否被處理完了。

          這里還有著重強(qiáng)調(diào)一點(diǎn),線程消息隊(duì)列是操作系統(tǒng)幫我們維護(hù)的一種資源,所以它的容量也是有限制的。 筆者曾經(jīng)做過實(shí)驗(yàn),在5~6秒事件內(nèi)調(diào)用PostThreadMessage往線程消息隊(duì)列里發(fā)送5萬多條消息,可是由于線程函數(shù)處理消息的速度遠(yuǎn)慢于發(fā)送速度,結(jié)果導(dǎo)致線程消息隊(duì)列里已經(jīng)堆滿了消息,而發(fā)送端還在發(fā)消息,最終導(dǎo)致消息隊(duì)列溢出,很多消息都丟失了。所以,如果你要在短時(shí)間內(nèi)往線程消息隊(duì)列里發(fā)送很多條消息,那就要判斷一下PostThreadMessage函數(shù)的返回值。當(dāng)消息隊(duì)列已經(jīng)溢出時(shí),此函數(shù)返回一個(gè)錯(cuò)誤值。根據(jù)返回值,你就可以控制是否繼續(xù)發(fā)送。

          工作線程給主線程發(fā)消息使用的是SendMessage和PostMessage函數(shù)。這兩個(gè)函數(shù)的區(qū)別在于SendMessage函數(shù)是阻塞方式,而 PostMessage函數(shù)是非阻塞方式。如果不是嚴(yán)格要求工作線程與主線程必須同步執(zhí)行,則推薦使用PostMessage。不要在線程函數(shù)體內(nèi)操作 MFC控件,因?yàn)槊總€(gè)線程都有自己的線程模塊狀態(tài)映射表,在一個(gè)線程中操作另一個(gè)線程中創(chuàng)建的MFC對象,會帶來意想不到的問題。更不要在線程函數(shù)里,直接調(diào)用UpdataData()函數(shù)更新用戶界面,這會導(dǎo)致程序直接crash。而應(yīng)該通過發(fā)送消息給主線程的方式,在主線程的消息響應(yīng)函數(shù)里操作控件。

          上面提到的SetProgress函數(shù)和AnalyseProc函數(shù)均為線程函數(shù),但它們都不能接收別的線程發(fā)過來的消息,雖然它們都可以給主線程發(fā)消息。它們要想能夠接收別的線程發(fā)過來的消息,則必須調(diào)用GetMessage或PeekMessage函數(shù)。這兩個(gè)函數(shù)的主要區(qū)別在于:
              GetMessage函數(shù)可以從消息隊(duì)列中抓取消息,當(dāng)抓取到消息后,GetMessage函數(shù)會將此條消息從消息隊(duì)列中刪除。而且,如果消息隊(duì)列中沒有消息,則GetMessage函數(shù)不會返回,CPU轉(zhuǎn)而回去執(zhí)行別的線程,釋放控制權(quán)。GetMessage返回的條件是抓取的消息是WM_QUIT。
              PeekMessage函數(shù)也可以從消息隊(duì)列中抓取消息,如果它的最后一個(gè)參數(shù)設(shè)置為PM_NOREMOVE,則不從消息隊(duì)列中刪除此條消息,此條消息會一直保留在消息隊(duì)列中。如果它的最后一個(gè)參數(shù)是PM_REMOVE,則會刪除此條消息。如果消息隊(duì)列中沒有消息,則PeekMessage函數(shù)會立刻返回,而不是像GetMessage一樣就那樣等在那兒。PeekMessage函數(shù)就像是窺探一下消息隊(duì)列,看看有沒有消息,有的話就處理,沒有就離開了。這一點(diǎn)也是兩個(gè)函數(shù)的最大不同。

      下面的代碼演示了在線程函數(shù)中使用這兩個(gè)函數(shù)的三種方式,這三種方法可以達(dá)到同樣的效果:

      void CThreadTest::SetSlider()
      {
      // 在線程函數(shù)里啟動(dòng)一個(gè)時(shí)鐘,每50毫秒發(fā)送一個(gè)WM_TIMER消息
             int nTimerID=::SetTimer(NULL,1,50,NULL);
             int nSliderPos=0;
             MSG msg;
             while (1)
             {
      //方式一    使用GetMessage函數(shù) 
      /*           if (::GetMessage(&msg,NULL,0,0))
                    {
                           switch(msg.message)
                           {
                           case WM_TIMER:
                                  {
                                         nSliderPos++;
                               ::SendMessage(this->m_hWnd,WM_SETSLIDERPOS,nSliderPos,0);
                                  }                        
                                  break;
                           case WM_QUIT_THREAD: //自定義消息
                                  {
                                         ::KillTimer(NULL,1);
                                         return;
                                  }                 
                               break;
                           default:
                               break;
                           }
                    }   
       */

      //方式二   使用PeekMessage函數(shù) 
      /*           if (::PeekMessage(&msg,NULL,0,0,PM_REMOVE))
                    {
                           switch(msg.message)
                           {
                           case WM_TIMER:
                                  {
                                         nSliderPos++;
                                         ::SendMessage(this->m_hWnd,WM_SETSLIDERPOS,nSliderPos,0);
                                  }                        
                                  break;
                           case WM_QUIT_THREAD: //自定義消息
                                  {
                                         ::KillTimer(NULL,1);
                                         return;
                                  }                 
                               break;
                           default:
                               break;
                           }
                    }
                    else
                    {
                           //必須有此操作,要不然當(dāng)沒有消息到來時(shí),線程函數(shù)相當(dāng)于陷
                           //入空循環(huán),cpu的占有率會飆升
                           Sleep(20);
                    }
      */

      //方式三   同時(shí)使用PeekMessage和GetMessage函數(shù) 
                    if (::PeekMessage(&msg,NULL,0,0,PM_NOREMOVE))
                    {
                           if(::GetMessage(&msg,NULL,0,0))
                           {
                                  switch(msg.message)
                                  {
                                  case WM_TIMER:
                                         {
                                                nSliderPos++;
                                                ::SendMessage(this->m_hWnd,WM_SETSLIDERPOS,nSliderPos,0);
                                         }                        
                                         break;
                                  case WM_QUIT_THREAD: //自定義消息
                                         {
                                                ::KillTimer(NULL,1);
                                                return;
                                         }                 
                                         break;
                                  default:
                                         break;
                                  }
                           }
                    }
                    else
                    {
                           Sleep(20);
                    }
             }
      }

          前面已經(jīng)介紹過了,不建議線程函數(shù)里用SendMessage給主線程發(fā)消息,因?yàn)檫@個(gè)函數(shù)是同步操作,就是如果SendMessage函數(shù)不執(zhí)行完,是不會返回的,這樣線程函數(shù)就無法繼續(xù)執(zhí)行。有時(shí)這種操作容易導(dǎo)致工作線程和主線程死鎖,這個(gè)我們后面會談到,會介紹一種解決方法。

      三 線程的退出

          線程的退出有多種方式,比如可以調(diào)用TerminateThread()函數(shù)強(qiáng)制線程退出,但不推薦這種方式,因?yàn)檫@樣做會導(dǎo)致線程中的資源來不及釋放。最好的也是推薦的方式,是讓線程函數(shù)自己退出。就像上面介紹的SetProgress()函數(shù)中,用全局變量g_exitThread使線程退出。

          而AnalyseProc用WAIT_OBJECT_0 ==WaitForSingleObject(m_eventExitAnalyse.m_hThread,0)這種方式來退出線程,還有在 SetSlider函數(shù)中利用發(fā)送自定義消息WM_QUIT_THREAD的方式令線程退出。這些都是可以使用的方法。

          當(dāng)主線程要退出時(shí),為了能保證線程的資源能全部地釋放,主線程必須等待工作線程退出。線程對象和進(jìn)程對象一樣,也是內(nèi)核對象,而且線程對象的特點(diǎn)是當(dāng)線程退出時(shí),線程內(nèi)核對象會自動(dòng)變?yōu)橛行盘枲顟B(tài),能夠喚醒所有正在等待它的線程。我們通常都習(xí)慣于使用WaitForSingleObject等函數(shù)來等待某個(gè)內(nèi)核對象變?yōu)橛行盘枲顟B(tài),但是我想說的是,在主線程中不要使用WaitForSingleObject和WaitForMultipleObjects 兩個(gè)函數(shù)等待線程退出,其原因就是有導(dǎo)致程序死鎖的隱患,特別是線程函數(shù)里調(diào)用了SendMessage或是直接操作了MFC對象,更易出現(xiàn)此種現(xiàn)象。

      下面的函數(shù)是一個(gè)在主線程中用來等待SetProgress()線程函數(shù)退出的函數(shù):

      //退出線程
      void CThreadTest::OnButton2()
      {
             g_exitThread=TRUE; //設(shè)置全局變量為真,令線程退出
      #if 1
             WaitForSingleObject(m_pThread1->m_hThread,INFINITE); //無限等待
      #else
             DWORD dRet;
             MSG msg;
             while (1)
             {
                    dRet=::MsgWaitForMultipleObjects(1,&m_pThread1->m_hThread,FALSE,INFINITE,QS_ALLINPUT);
                    if (dRet == WAIT_OBJECT_0+1)
                    {
                           while (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
                           {
                                  TranslateMessage(&msg);
                                  DispatchMessage(&msg);
                           }
                    }
                    else
                    {
                           break;
                    }
             }
           
      #endif   
      }

          在上面的函數(shù)中我用#if #else #endif這組預(yù)編譯指令控制函數(shù)的執(zhí)行代碼,如果我令#if 1,則執(zhí)行WaitForSingleObject函數(shù),如果我令#if 0,則執(zhí)行DWORD dRet路徑。首先令#if  1,測試會發(fā)現(xiàn),程序死鎖了。原因是當(dāng)程序執(zhí)行到WaitForSingleObject函數(shù)時(shí),主線程掛起,等待線程函數(shù)退出,此時(shí)CPU切換到線程函數(shù)體內(nèi)執(zhí)行,如果執(zhí)行到if (g_exitThread)處,則線程函數(shù)順利退出,可如果執(zhí)行到m_progress.SetPos(nCount)處,由于SetPos函數(shù)是在主線程中完成的操作,Windows是基于消息的操作系統(tǒng),很多操作都是靠發(fā)消息完成的,由于主線程已經(jīng)掛起,所以沒有機(jī)會去消息隊(duì)列中抓取消息并處理它,結(jié)果導(dǎo)致SetPos函數(shù)不會返回,工作線程也被掛起,典型的死鎖。如果不用m_progress.SetPos,而改用 this->SendMessage(…),其結(jié)果是一樣的。此時(shí)如果用了PostMessage,則工作線程會順利退出,因?yàn)?PostMessage是異步執(zhí)行的。由此可見,在主線程中用WaitForSingleObject等待工作線程退出是有很大隱患的。

          為解決這一問題,微軟特提供了一個(gè)MsgWaitForMultipleObjects函數(shù),該函數(shù)的特點(diǎn)是它不但可以等待內(nèi)核對象,還可以等消息。也就是當(dāng)有消息到來時(shí),該函數(shù)也一樣可以返回,并處理消息,這樣就給了工作線程退出的機(jī)會。

      DWORD MsgWaitForMultipleObjects(
      DWORD nCount, //要等待的內(nèi)核對象數(shù)目
      LPHANDLE pHandles, //要等待的內(nèi)核對象句柄數(shù)組指針
      BOOL fWaitAll, //是等待全部對象還是單個(gè)對象
      DWORD dwMilliseconds,//等待時(shí)間
      DWORD dwWakeMask );//等待的消息類型

      下面就詳解一下該函數(shù)的參數(shù)使用方法:
          DWORD nCount:要等待的內(nèi)核對象的數(shù)目。如果等待兩個(gè)線程退出,則nCount=2;
          LPHANDLE pHandles:要等待的內(nèi)核對象句柄數(shù)組指針。
              如果只要等待一個(gè)線程退出,則直接設(shè)置該線程句柄的指針即可:
              MsgWaitForMultipleObjects(1,&m_pThread->m_hThread,…)
             
              如果要等待兩個(gè)線程退出,則使用方法為:
              HANDLE hArray[2]={ m_pThread1->m_hThread , m_pThread2->m_hThread };
              MsgWaitForMultipleObjects(2,hArray,…)
          BOOL fWaitAll:TRUE-表示只有要等待的線程全部退出后,此函數(shù)才返回,
                                   FALSE-表示要等待的線程中任意一個(gè)退出了,或是有消息到達(dá)了,此函數(shù)均會返回。


               在上面的OnButton2()函數(shù)中,我要等待一個(gè)線程退出,將fWaitAll設(shè)置為FALSE,目的是無論是線程真的退出了,還是有消息到達(dá)了,該函數(shù)都能返回。如果將該fWaitAll設(shè)置為TRUE,那么函數(shù)返回的唯一條件是線程退出了,即便是有消息到來了,該函數(shù)也一樣不會返回。
          DWORD dwMilliseconds:等待的事件,單位是毫秒??梢栽O(shè)置為INFINITE,無窮等待
          DWORD dwWakeMask:等待的消息類型,通??梢栽O(shè)置為QS_ALLINPUT。此宏表示的是可以等待任意類型的消息。當(dāng)然,也可以指定等待的消息類型。
              #define QS_ALLINPUT (QS_INPUT| \
                          QS_POSTMESSAGE   | \
                          QS_TIMER         | \
                          QS_PAINT         | \
                          QS_HOTKEY        | \
                          QS_SENDMESSAGE)

          返回值:DWORD dRet 通過函數(shù)返回值,可以得到一些有效信息。函數(shù)返回值依fWaitAll設(shè)置的不同而有所不同。下面是函數(shù)返回值的幾種常見類型:
              dRet = 0xFFFFFFFF :   表示函數(shù)調(diào)用失敗,可用GetLastError()得到具體的出錯(cuò)信息;
              dRet =WAIT_OBJECT_0+nCount:表示有消息到達(dá)了;
              如果fWaitAll設(shè)置為TRUE
              dRet = WAIT_OBJECT_0,表示所有等待的核心對象都激發(fā)了,或是線程都退出了;
              如果fWaitAll設(shè)置為FALSE
              dRet = WAIT_OBJECT_0 ~ WAIT_OBJECT_0+nCount-1:表示等待的內(nèi)核對象被激發(fā)了,index=dRet - WAIT_OBJECT_0,表示hArray[]數(shù)組中索引為index的那個(gè)對象被激發(fā)了。
       
              當(dāng)函數(shù)由于消息到來而返回,則需要用戶主動(dòng)去消息隊(duì)列中將消息抓取出來,然后派發(fā)出去,這樣該消息就會被處理了。其具體的操作就是:
              while (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
              {
                     TranslateMessage(&msg);
                     DispatchMessage(&msg);
              }


      下面再看一個(gè)用這個(gè)函數(shù)等待兩個(gè)線程退出的例子:

      //關(guān)閉線程1和2
      void CThreadTest::OnButton6()
      {
             …
             …
             DWORD dRet=-2;
             HANDLE hArray[2];
           
             hArray[0]=m_pThread1->m_hThread;
             hArray[1]=m_pThread2->m_hThread;
             MSG msg;
             int nExitThreadCount=0;       //標(biāo)記已經(jīng)有幾個(gè)線程退出了
             BOOL bWaitAll=FALSE;
             int nWaitCount=2;    //初始等待的線程數(shù)目
             while (1)
             {
                    dRet=MsgWaitForMultipleObjects(nWaitCount,hArray,bWaitAll,INFINITE,QS_ALLINPUT);
                    if (dRet == WAIT_OBJECT_0+ nWaitCount)
                    {
                           TRACE("收到消息,函數(shù)返回值為%d \n",dRet);
                           while (PeekMessage(&msg,NULL,0,0,PM_REMOVE))
                           {
                                  TranslateMessage(&msg);
                                  DispatchMessage(&msg);
                           }
                         
                    }
                    else if (dRet >= WAIT_OBJECT_0 && dRet < WAIT_OBJECT_0+ nWaitCount)
                    {
                           nExitThreadCount++;
                           if (nExitThreadCount == 1)
                           {
                                  TRACE("一個(gè)線程退出了\n");
                                  int nIndex=dRet-WAIT_OBJECT_0;
                                  hArray[nIndex]=hArray[nWaitCount-1];
                                  hArray[nWaitCount-1]=NULL;
                                  nWaitCount--;
                           }
                           else
                           {
                                  TRACE("兩個(gè)線程都退出了\n");
                                  break;
                           }
                    }
                    else
                    {
                           DWORD dErrCode=GetLastError();
                           …
                           break;
                    }
             }
      }

          在上面這個(gè)例子中,我將bWaitAll設(shè)置為FALSE,目的是當(dāng)我要等待的兩個(gè)線程中由一個(gè)退出了,或是有消息到來了,此函數(shù)都可以退出。如果我將此參數(shù)設(shè)置為TRUE,那么,當(dāng)且僅當(dāng)我要等待的兩個(gè)線程均退出了,這個(gè)函數(shù)才會返回,這種使用方法有是程序陷入死鎖的危險(xiǎn),故應(yīng)避免。無論是等待一個(gè)還是多個(gè)線程,只需將此參數(shù)設(shè)置為FALSE即可,然后通過函數(shù)返回值判斷究竟是那個(gè)返回了,還是消息到達(dá)了即可。這一要點(diǎn)前面已有陳述,此處再強(qiáng)調(diào)一遍。

      通過函數(shù)返回值可以得知究竟哪個(gè)線程退出了,當(dāng)要等待的兩個(gè)線程中的一個(gè)已經(jīng)退出后,則應(yīng)該從新設(shè)置等待函數(shù)的參數(shù),對等待的句柄數(shù)組進(jìn)行整理。

      {
          int nIndex=dRet-WAIT_OBJECT_0;
         
          hArray[nIndex]=hArray[nWaitCount-1];
         
          hArray[nWaitCount-1]=NULL;
         
          nWaitCount--;
      }

      這組語句就是用來從新設(shè)置參數(shù)的,其過程就是將等待的總數(shù)目減一,并將剛退出的線程的句柄設(shè)置為NULL,移到數(shù)組的最末位置。

      上面介紹了線程函數(shù)的設(shè)計(jì)以及在主線程中等待工作線程退出的方法,著重介紹了MsgWaitForMultipleObjects函數(shù)的使用要點(diǎn),希望對大家有所幫助,也希望大家能提寶貴意見,補(bǔ)我之不足,愿與大家共同進(jìn)步。

        本站是提供個(gè)人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點(diǎn)擊一鍵舉報(bào)。
        轉(zhuǎn)藏 分享 獻(xiàn)花(0

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多