线程函数的设计以及MsgWaitForMultipleObjects函数的使用要点

本文详细介绍了在C++中如何使用多线程和消息机制实现复杂任务的并发处理,包括线程的创建、退出、消息传递以及资源管理。重点探讨了线程之间的同步、互斥与消息队列的使用,通过实例展示了如何在主线程中等待子线程退出,并提供了防止程序死锁的解决方案。
摘要由CSDN通过智能技术生成
 

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;
}
void CThreadTest::SetSlider()
{
 
//  在线程函数里启动一个时钟,每50毫秒发送一个WM_TIMER消息
       int nTimerID=::SetTimer(NULL,1,50,NULL);
 
       int nSliderPos=0;
 
       MSG msg;
       while (1)
       {
//方式一    使用GetMessage函数  
 
//方式二   使用PeekMessage函数  
 
 
//方式三   同时使用PeekMessage和GetMessage函数  
 
              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);
              }
       }
三线程的退出
 
线程的退出有多种方式,比如可以调用TerminateThread()函数强制线程退出,但不推荐这种方式,因为这样做会导致线程中的资源来不及释放。最好的也是推荐的方式,是让线程函数自己退出。就像上面介绍的SetProgress()函数中,用全局变量g_exitThread使线程退出。
而AnalyseProc用WAIT_OBJECT_0 ==WaitForSingleObject(m_eventExitAnalyse.m_hThread,0)这种方式来退出线程,还有在SetSlider函数中利用发送自定义消息WM_QUIT_THREAD的方式令线程退出。这些都是可以使用的方法。
       当主线程要退出时,为了能保证线程的资源能全部地释放,主线程必须等待工作线程退出。线程对象和进程对象一样,也是内核对象,而且线程对象的特点是当线程退出时,线程内核对象会自动变为有信号状态,能够唤醒所有正在等待它的线程。我们通常都习惯于使用WaitForSingleObject等函数来等待某个内核对象变为有信号状态,但是我想说的是,在主线程中不要使用WaitForSingleObject和WaitForMultipleObjects两个函数等待线程退出,其原因就是有导致程序死锁的隐患,特别是线程函数里调用了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(DWORDnCount,LPHANDLEpHandles,BOOLfWaitAll,DWORDdwMilliseconds,DWORDdwWakeMask);
 
下面就详解一下该函数的参数使用方法:
DWORDnCount:要等待的内核对象的数目。如果等待两个线程退出,则nCount=2;
LPHANDLEpHandles:要等待的内核对象句柄数组指针。
 
如果只要等待一个线程退出,则直接设置该线程句柄的指针即可:
MsgWaitForMultipleObjects(1,&m_pThread->m_hThread,…)
 
如果要等待两个线程退出,则使用方法为:
HANDLE hArray[2]={m_pThread1->m_hThread ,m_pThread2->m_hThread };
MsgWaitForMultipleObjects(2,hArray,…)
 
BOOLfWaitAll: TRUE-表示只有要等待的线程全部退出后,此函数才返回,
               FALSE-表示要等待的线程中任意一个退出了,或是有消息到达了,此函数均会返回。
在上面的OnButton2()函数中,我要等待一个线程退出,将fWaitAll设置为
FALSE,目的是无论是线程真的退出了,还是有消息到达了,该函数都能返回。
如果将该fWaitAll设置为TRUE,那么函数返回的唯一条件是线程退出了,即便
是有消息到来了,该函数也一样不会返回。
 
DWORDdwMilliseconds:等待的事件,单位是毫秒。可以设置为INFINITE,无
穷等待
 
DWORDdwWakeMask:等待的消息类型,通常可以设置为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);
}
 
下面再看一个用这个函数等待两个线程退出的例子:
//关闭线程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;       //标记已经有几个线程退出了
       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--;
}
 
  

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值