• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            大龍的博客

            常用鏈接

            統計

            最新評論

            線程函數的設計以及MsgWaitForMultipleObjects函數的使用要點 ----- 轉

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

             

            創建線程

             

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

            //線程函數,類的靜態成員函數

            UINT CThreadTest::TH_SetProgress(LPVOID lpVoid)

            {

                   CThreadTest *pTest=(CThreadTest *)lpVoid;

                   pTest->SetProgress();

                   return 0;

            }

            //類的成員函數,此函數執行實際的線程函數操作,卻可以自如的調用成員數據

            void CThreadTest::SetProgress()

            {

            int nCount=0;

                   while (1)

                   {

                          m_progress.SetPos(nCount); //設置進度條進度

            //            this->SendMessage(WM_SETPROGRESSPOS,nCount,0);//也可以采用這種方式設置

                          nCount++;

                          if (g_exitThread)

                          {

                                 return;

                          }

                          Sleep(200);

                   }

            }

             

            線程函數體的設計

             

            有過多線程設計經驗的人都有體會,多線程設計最重要的就是要處理好線程間的同步和通訊問題。如解決不好這個問題,會給程序帶來潛藏的隱患。線程的同步可以利用臨界區、事件、互斥體和信號量來實現,線程間的通訊可利用全局變量和發消息的形式實現。其中事件和臨界區是使用得比較多的工具。請看下面的線程函數體:

            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)

                                 {

                                        //繼續分析

                                        //

                                 }

                          }

                   }

             

                   return 0;

            }

             

            上面的線程函數用到了三個事件變量eventStartAnalyseeventExitAnalyseeventPause,分別用來控制線程函數的啟動、退出以及暫停。再配以WaitForSingleObject函數,就可以自如的控制線程函數的執行,這是在線程函數體內應用事件變量的典型方式,也是推薦的方式。

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

            這里還有著重強調一點,線程消息隊列是操作系統幫我們維護的一種資源,所以它的容量也是有限制的。筆者曾經做過實驗,在5~6秒事件內調用PostThreadMessage往線程消息隊列里發送5萬多條消息,可是由于線程函數處理消息的速度遠慢于發送速度,結果導致線程消息隊列里已經堆滿了消息,而發送端還在發消息,最終導致消息隊列溢出,很多消息都丟失了。所以,如果你要在短時間內往線程消息隊列里發送很多條消息,那就要判斷一下PostThreadMessage函數的返回值。當消息隊列已經溢出時,此函數返回一個錯誤值。根據返回值,你就可以控制是否繼續發送。

            工作線程給主線程發消息使用的是SendMessagePoseMessage函數。這兩個函數的區別在于SendMessage函數是阻塞方式,而PoseMessage函數是非阻塞方式。如果不是嚴格要求工作線程與主線程必須同步執行,則推薦使用PoseMessage。不要在線程函數體內操作MFC控件,因為每個線程都有自己的線程模塊狀態映射表,在一個線程中操作另一個線程中創建的MFC對象,會帶來意想不到的問題。更不要在線程函數里,直接調用UpdataData()函數更新用戶界面,這會導致程序直接crash。而應該通過發送消息給主線程的方式,在主線程的消息響應函數里操作控件。上面提到的SetProgress函數和AnalyseProc函數均為線程函數,但它們都不能接收別的線程發過來的消息,雖然它們都可以給主線程發消息。它們要想能夠接收別的線程發過來的消息,則必須調用GetMessagePeekMessage函數。這兩個函數的主要區別在于:

            GetMessage函數可以從消息隊列中抓取消息,當抓取到消息后,GetMessage函數會將此條消息從消息隊列中刪除。而且,如果消息隊列中沒有消息,則GetMessage函數不會返回,CPU轉而回去執行別的線程,釋放控制權。GetMessage返回的條件是抓取的消息是WM_QUIT

            PeekMessage函數也可以從消息隊列中抓取消息,如果它的最后一個參數設置為PM_NOREMOVE,則不從消息隊列中刪除此條消息,此條消息會一直保留在消息隊列中。如果它的最后一個參數是PM_REMOVE,則會刪除此條消息。如果消息隊列中沒有消息,則PeekMessage函數會立刻返回,而不是像GetMessage一樣就那樣等在那兒。PeekMessage函數就像是窺探一下消息隊列,看看有沒有消息,有的話就處理,沒有就離開了。這一點也是兩個函數的最大不同。下面的代碼演示了在線程函數中使用這兩個函數的三種方式,這三種方法可以達到同樣的效果:

            void CThreadTest::SetSlider()

            {

             

            // 在線程函數里啟動一個時鐘,每50毫秒發送一個WM_TIMER消息

                   int nTimerID=::SetTimer(NULL,1,50,NULL);

             

                   int nSliderPos=0;

             

                   MSG msg;

                   while (1)

                   {

            //方式一    使用GetMessage函數  

            /*           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函數  

             

            /*           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

                          {

                                   //必須有此操作,要不然當沒有消息到來時,線程函數相當于陷

            //入空循環,cpu的占有率會飆升

                                 Sleep(20);

                          }

            */

             

            //方式三   同時使用PeekMessageGetMessage函數  

             

                          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);

                          }

                   }

            }

            前面已經介紹過了,不建議線程函數里用SendMessage給主線程發消息,因為這個函數是同步操作,就是如果SendMessage函數不執行完,是不會返回的,這樣線程函數就無法繼續執行。有時這種操作容易導致工作線程和主線程死鎖,這個我們后面會談到,會介紹一種解決方法。

             

            線程的退出

             

            線程的退出有多種方式,比如可以調用TerminateThread()函數強制線程退出,但不推薦這種方式,因為這樣做會導致線程中的資源來不及釋放。最好的也是推薦的方式,是讓線程函數自己退出。就像上面介紹的SetProgress()函數中,用全局變量g_exitThread使線程退出。

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

                   當主線程要退出時,為了能保證線程的資源能全部地釋放,主線程必須等待工作線程退出。線程對象和進程對象一樣,也是內核對象,而且線程對象的特點是當線程退出時,線程內核對象會自動變為有信號狀態,能夠喚醒所有正在等待它的線程。我們通常都習慣于使用WaitForSingleObject等函數來等待某個內核對象變為有信號狀態,但是我想說的是,在主線程中不要使用WaitForSingleObjectWaitForMultipleObjects兩個函數等待線程退出,其原因就是有導致程序死鎖的隱患,特別是線程函數里調用了SendMessage或是直接操作了MFC對象,更易出現此種現象。下面的函數是一個在主線程中用來等待SetProgress()線程函數退出的函數:

             

            //退出線程

            void CThreadTest::OnButton2()

            {

                   g_exitThread=TRUE; //設置全局變量為真,令線程退出

             

            #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    

            }

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

                   為解決這一問題,微軟特提供了一個MsgWaitForMultipleObjects函數,該函數的特點是它不但可以等待內核對象,還可以等消息。也就是當有消息到來時,該函數也一樣可以返回,并處理消息,這樣就給了工作線程退出的機會。

            DWORD MsgWaitForMultipleObjects(
            DWORD nCount, //要等待的內核對象數目
            LPHANDLE pHandles, //要等待的內核對象句柄數組指針
            BOOL fWaitAll, //是等待全部對象還是單個對象
            DWORD dwMilliseconds,//等待時間 
            DWORD dwWakeMask );//等待的消息類型
             
            下面就詳解一下該函數的參數使用方法:
            DWORD nCount:要等待的內核對象的數目。如果等待兩個線程退出,則nCount=2
            LPHANDLE pHandles:要等待的內核對象句柄數組指針。
             
            如果只要等待一個線程退出,則直接設置該線程句柄的指針即可:
            MsgWaitForMultipleObjects(1,&m_pThread->m_hThread,…)
             
            如果要等待兩個線程退出,則使用方法為:
            HANDLE hArray[2]={ m_pThread1->m_hThread , m_pThread2->m_hThread }
            MsgWaitForMultipleObjects(2,hArray,…)
             
            BOOL fWaitAllTRUE-表示只有要等待的線程全部退出后,此函數才返回,
                           FALSE-表示要等待的線程中任意一個退出了,或是有消息到達了,此函數均會返回。
            在上面的OnButton2()函數中,我要等待一個線程退出,將fWaitAll設置為
            FALSE,目的是無論是線程真的退出了,還是有消息到達了,該函數都能返回。
            如果將該fWaitAll設置為TRUE,那么函數返回的唯一條件是線程退出了,即便
            是有消息到來了,該函數也一樣不會返回。
             
            DWORD dwMilliseconds:等待的事件,單位是毫秒。可以設置為INFINITE,無
            窮等待
             
            DWORD dwWakeMask:等待的消息類型,通常可以設置為QS_ALLINPUT。此宏表示的是可以等待任意類型的消息。當然,也可以指定等待的消息類型。
             
            #define QS_ALLINPUT        (QS_INPUT         | \
                                        QS_POSTMESSAGE   | \
                                        QS_TIMER         | \
                                        QS_PAINT         | \
                                        QS_HOTKEY        | \
                                        QS_SENDMESSAGE)
             

            返回值:DWORD dRet 通過函數返回值,可以得到一些有效信息。函數返回值依fWaitAll設置的不同而有所不同。下面是函數返回值的幾種常見類型:

            dRet = 0xFFFFFFFF    表示函數調用失敗,可用GetLastError()得到具體的出錯信息;

            dRet =WAIT_OBJECT_0+nCount:表示有消息到達了;

             

            如果fWaitAll設置為TRUE

            dRet = WAIT_OBJECT_0,表示所有等待的核心對象都激發了,或是線程都退出了;

            如果fWaitAll設置為FALSE

            dRet = WAIT_OBJECT_0 ~ WAIT_OBJECT_0+nCount-1:表示等待的內核對象被激發了,index=dRet - WAIT_OBJECT_0,表示hArray[]數組中索引為index的那個對象被激發了。

             

            當函數由于消息到來而返回,則需要用戶主動去消息隊列中將消息抓取出來,然后派發出去,這樣該消息就會被處理了。其具體的操作就是:

            while (PeekMessage(&msg,NULL,0,0,PM_REMOVE))

            {

                   TranslateMessage(&msg);

                   DispatchMessage(&msg);

            }

             

            下面再看一個用這個函數等待兩個線程退出的例子:

            //關閉線程12

            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;       //標記已經有幾個線程退出了

                   BOOL bWaitAll=FALSE;

                   int nWaitCount=2;    //初始等待的線程數目

             

                   while (1)

                   {

                          dRet=MsgWaitForMultipleObjects(nWaitCount,hArray,bWaitAll,INFINITE,QS_ALLINPUT);

                          if (dRet == WAIT_OBJECT_0+ nWaitCount)

                          {

                                 TRACE("收到消息,函數返回值為%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("一個線程退出了\n");

                                        int nIndex=dRet-WAIT_OBJECT_0;

                                        hArray[nIndex]=hArray[nWaitCount-1];

                                        hArray[nWaitCount-1]=NULL;

                                        nWaitCount--;

             

                                 }

                                 else

                                 {

                                        TRACE("兩個線程都退出了\n");

                                        break;

                                 }

                          }

                          else

                          {

                                 DWORD dErrCode=GetLastError();

                                

                                 break;

                          }

                   }

                  

            }

             

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

            通過函數返回值可以得知究竟哪個線程退出了,當要等待的兩個線程中的一個已經退出后,則應該從新設置等待函數的參數,對等待的句柄數組進行整理。

            {

            int nIndex=dRet-WAIT_OBJECT_0;

            hArray[nIndex]=hArray[nWaitCount-1];

            hArray[nWaitCount-1]=NULL;

            nWaitCount--;

            }

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

             

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

            posted on 2009-04-01 01:04 大龍 閱讀(7770) 評論(6)  編輯 收藏 引用

            評論

            # re: 線程函數的設計以及MsgWaitForMultipleObjects函數的使用要點 ----- 轉[未登錄] 2009-08-05 14:05 kevin

            好文章,解釋得很清楚  回復  更多評論   

            # re: 線程函數的設計以及MsgWaitForMultipleObjects函數的使用要點 ----- 轉 2010-01-23 22:47 路過

            ::KillTimer(NULL,1);語句有問題,事實并沒有停止TImer。改為killTimer(NULL,nTimerID)  回復  更多評論   

            # re: 線程函數的設計以及MsgWaitForMultipleObjects函數的使用要點 ----- 轉 2010-05-20 10:43 ztest8

            GooD!   回復  更多評論   

            # re: 線程函數的設計以及MsgWaitForMultipleObjects函數的使用要點 ----- 轉 2011-08-15 11:32 okay

            int nIndex=dRet-WAIT_OBJECT_0;

            hArray[nIndex]=hArray[nWaitCount-1];

            hArray[nWaitCount-1]=NULL;

            nWaitCount--;


            此處有問題,對于多個線程,如果第一個到最后第二個可以這么寫,對于數組中的最后一個直接
            hArray[nWaitCount-1]=NULL;

            nWaitCount--;
            就行了  回復  更多評論   

            # re: 線程函數的設計以及MsgWaitForMultipleObjects函數的使用要點 ----- 轉 2012-12-19 10:15 12313

            int nIndex=dRet-WAIT_OBJECT_0;

            hArray[nIndex]=hArray[nWaitCount-1];

            hArray[nWaitCount-1]=NULL;

            nWaitCount--;

            這個顯然有問題
            你怎么知道 hArray[nWaitCount-1] 就不是hArray[nIndex]?如果nIndex 等于1的話,這幾句就錯了。  回復  更多評論   

            # re: 線程函數的設計以及MsgWaitForMultipleObjects函數的使用要點 ----- 轉 2014-11-06 20:18 breeze

            將hArray[nIndex]=hArray[nWaitCount-1];改為:
            for (int i = 0; i < nWaitCount - 1; ++i) { //[更正]刪除數組中已退出的線程
            if (i == nIndex) {
            hArray[i]=hArray[nWaitCount-1];
            break;
            }
            }
              回復  更多評論   

            人妻无码久久一区二区三区免费| 亚洲人成伊人成综合网久久久| 久久久久久精品免费看SSS| 久久国产精品99久久久久久老狼| 国产aⅴ激情无码久久| 中文精品久久久久人妻| 亚洲国产精品综合久久一线| 国产精品九九久久免费视频 | 精品久久久久久中文字幕人妻最新| 欧美精品一区二区久久| 久久人人爽人人爽人人片AV麻豆 | 97精品国产97久久久久久免费| 亚洲国产一成久久精品国产成人综合| 精品多毛少妇人妻AV免费久久 | 久久精品国内一区二区三区| 69SEX久久精品国产麻豆| 天天爽天天爽天天片a久久网| 亚洲天堂久久精品| 久久精品国产亚洲AV不卡| 日本精品久久久久久久久免费| 最新久久免费视频| 少妇人妻88久久中文字幕| 精品久久8x国产免费观看| 久久精品国产精品青草app| 久久国产精品波多野结衣AV| 久久天天躁狠狠躁夜夜2020老熟妇| 久久久久婷婷| 国内精品综合久久久40p| 精品久久久久久无码中文字幕一区| 国产精品一久久香蕉国产线看| 久久中文娱乐网| 亚洲国产成人精品久久久国产成人一区二区三区综 | 久久精品无码一区二区三区日韩| 久久天天躁狠狠躁夜夜不卡| 欧美激情一区二区久久久| 久久国产高潮流白浆免费观看| 99久久亚洲综合精品网站| 久久亚洲sm情趣捆绑调教| 久久亚洲欧美国产精品| 精品国产综合区久久久久久| 国产69精品久久久久久人妻精品|