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

上传人:b****5 文档编号:12362741 上传时间:2023-04-18 格式:DOCX 页数:16 大小:20.91KB
下载 相关 举报
线程函数的设计以及MsgWaitForMultipleObjects函数的使用要点.docx_第1页
第1页 / 共16页
线程函数的设计以及MsgWaitForMultipleObjects函数的使用要点.docx_第2页
第2页 / 共16页
线程函数的设计以及MsgWaitForMultipleObjects函数的使用要点.docx_第3页
第3页 / 共16页
线程函数的设计以及MsgWaitForMultipleObjects函数的使用要点.docx_第4页
第4页 / 共16页
线程函数的设计以及MsgWaitForMultipleObjects函数的使用要点.docx_第5页
第5页 / 共16页
点击查看更多>>
下载资源
资源描述

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

《线程函数的设计以及MsgWaitForMultipleObjects函数的使用要点.docx》由会员分享,可在线阅读,更多相关《线程函数的设计以及MsgWaitForMultipleObjects函数的使用要点.docx(16页珍藏版)》请在冰豆网上搜索。

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

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

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

使用多线程技术可以显著地提高程序性能,本文就讲讲在程序中如何使用工作线程,以及工作线程与主线程通讯的问题。

 

一 创建线程

使用MFC提供的全局函数AfxBeginThread()即可创建一个工作线程。

线程函数的标准形式为 UINT MyFunProc(LPVOID);此函数既可以是全局函数,也可以是类的静态成员函数。

之所以必须是静态成员函数,是由于类的非静态成员函数,编译器在编译时会自动加上一个this指针参数,如果将函数设置为静态的成员函数,则可以消除this指针参数。

如果想在线程函数中任意调用类的成员变量(此处指的是数据成员,而不是控件关联的成员变量),则可以将类的指针作为参数传递给线程函数,然后经由该指针,就可以调用类的成员变量了。

//线程函数,类的静态成员函数

UINTCThreadTest:

:

TH_SetProgress(LPVOIDlpVoid)

{

       CThreadTest*pTest=(CThreadTest*)lpVoid;

       pTest->SetProgress();

       return0;

}

//类的成员函数,此函数执行实际的线程函数操作,却可以自如的调用成员数据

voidCThreadTest:

:

SetProgress()

{

intnCount=0;

       while

(1)

       {

              m_progress.SetPos(nCount);//设置进度条进度

//this->SendMessage(WM_SETPROGRESSPOS,nCount,0);//也可用这种方式

              nCount++;

              if(g_exitThread)

              {

                     return;

              }

              Sleep(200);

       }

}

 

二 线程函数体的设计

有过多线程设计经验的人都有体会,多线程设计最重要的就是要处理好线程间的同步和通讯问题。

如解决不好这个问题,会给程序带来潜藏的隐患。

线程的同步可以利用临界区、事件、互斥体和信号量来实现,线程间的通讯可利用全局变量和发消息的形式实现。

其中事件和临界区是使用得比较多的工具。

请看下面的线程函数体:

UINTAnalyseProc(LPVOID   lVOID)

{

       if(WAIT_OBJECT_0==WaitForSingleObject(m_eventStartAnalyse.m_hThread,INFINITE))

       {

              while(WAIT_OBJECT_0==WaitForSingleObject(m_eventExitAnalyse.m_hThread,0))

              {

                     DWORDdRet=WaitForSingleObject(m_eventPause.m_hThread,0);

                     if(dRet==WAIT_OBJECT_0)

                     {

                            //暂停分析

                            Sleep(10);

                     }

                     elseif(dRet==WAIT_TIMEOUT)

                     {

                            //继续分析

                            //

                     }

              }

       }

       return0;

}

上面的线程函数用到了三个事件变量eventStartAnalyse、eventExitAnalyse和eventPause,分别用来控制线程函数的启动、退出以及暂停。

再配以WaitForSingleObject函数,就可以自如的控制线程函数的执行,这是在线程函数体内应用事件变量的典型方式,也是推荐的方式。

无论是工作线程还是用户界面线程,都有消息队列,都可以接收别的线程发过来的消息也可以给别的线程发送消息。

给工作线程发消息使用的函数是PostThreadMessage()。

此函数的第一个参数是接收消息的线程的ID。

此函数是异步执行的,机制和PostMessage一样,就是把消息抛出后就立即返回,不理会消息是否被处理完了。

这里还有着重强调一点,线程消息队列是操作系统帮我们维护的一种资源,所以它的容量也是有限制的。

笔者曾经做过实验,在5~6秒事件内调用PostThreadMessage往线程消息队列里发送5万多条消息,可是由于线程函数处理消息的速度远慢于发送速度,结果导致线程消息队列里已经堆满了消息,而发送端还在发消息,最终导致消息队列溢出,很多消息都丢失了。

所以,如果你要在短时间内往线程消息队列里发送很多条消息,那就要判断一下PostThreadMessage函数的返回值。

当消息队列已经溢出时,此函数返回一个错误值。

根据返回值,你就可以控制是否继续发送。

工作线程给主线程发消息使用的是SendMessage和PoseMessage函数。

这两个函数的区别在于SendMessage函数是阻塞方式,而PoseMessage函数是非阻塞方式。

如果不是严格要求工作线程与主线程必须同步执行,则推荐使用PoseMessage。

不要在线程函数体内操作MFC控件,因为每个线程都有自己的线程模块状态映射表,在一个线程中操作另一个线程中创建的MFC对象,会带来意想不到的问题。

更不要在线程函数里,直接调用UpdataData()函数更新用户界面,这会导致程序直接crash。

而应该通过发送消息给主线程的方式,在主线程的消息响应函数里操作控件。

上面提到的SetProgress函数和AnalyseProc函数均为线程函数,但它们都不能接收别的线程发过来的消息,虽然它们都可以给主线程发消息。

它们要想能够接收别的线程发过来的消息,则必须调用GetMessage或PeekMessage函数。

这两个函数的主要区别在于:

GetMessage函数可以从消息队列中抓取消息,当抓取到消息后,GetMessage函数会将此条消息从消息队列中删除。

而且,如果消息队列中没有消息,则GetMessage函数不会返回,CPU转而回去执行别的线程,释放控制权。

GetMessage返回的条件是抓取的消息是WM_QUIT。

PeekMessage函数也可以从消息队列中抓取消息,如果它的最后一个参数设置为PM_NOREMOVE,则不从消息队列中删除此条消息,此条消息会一直保留在消息队列中。

如果它的最后一个参数是PM_REMOVE,则会删除此条消息。

如果消息队列中没有消息,则PeekMessage函数会立刻返回,而不是像GetMessage一样就那样等在那儿。

PeekMessage函数就像是窥探一下消息队列,看看有没有消息,有的话就处理,没有就离开了。

这一点也是两个函数的最大不同。

下面的代码演示了在线程函数中使用这两个函数的三种方式,这三种方法可以达到同样的效果:

voidCThreadTest:

:

SetSlider()

{

// 在线程函数里启动一个时钟,每50毫秒发送一个WM_TIMER消息

       intnTimerID=:

:

SetTimer(NULL,1,50,NULL);

       intnSliderPos=0;

       MSGmsg;

       while

(1)

       {

//方式一    使用GetMessage函数  

/*           if(:

:

GetMessage(&msg,NULL,0,0))

              {

                     switch(msg.message)

                     {

                     caseWM_TIMER:

                            {

                                   nSliderPos++;

                         :

:

SendMessage(this->m_hWnd,WM_SETSLIDERPOS,nSliderPos,0);

                            }                         

                            break;

                     caseWM_QUIT_THREAD:

 //自定义消息

                            {

                                   :

:

KillTimer(NULL,1);

                                   return;

                            }                  

                         break;

                     default:

                         break;

                     }

              }    

 */

 

//方式二   使用PeekMessage函数  

/*           if(:

:

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

              {

                     switch(msg.message)

                     {

                     caseWM_TIMER:

                            {

                                   nSliderPos++;

                                   :

:

SendMessage(this->m_hWnd,WM_SETSLIDERPOS,nSliderPos,0);

                            }                         

                            break;

                     caseWM_QUIT_THREAD:

//自定义消息

                            {

                                   :

:

KillTimer(NULL,1);

                                   return;

                            }                  

                         break;

                     default:

                         break;

                     }

              }

              else

              {

                       //必须有此操作,要不然当没有消息到来时,线程函数相当于陷

//入空循环,cpu的占有率会飙升

                     Sleep(20);

              }

*/

 

//方式三   同时使用PeekMessage和GetMessage函数  

              if(:

:

PeekMessage(&msg,NULL,0,0,PM_NOREMOVE))

              {

                     if(:

:

GetMessage(&msg,NULL,0,0))

                     {

                            switch(msg.message)

                            {

                            caseWM_TIMER:

                                   {

                                          nSliderPos++;                                                                    :

:

SendMessage(this->m_hWnd,WM_SETSLIDERPOS,nSliderPos,0);

                                   }                         

                                   break;

                            caseWM_QUIT_THREAD:

//自定义消息

                                   {

                                          :

:

KillTimer(NULL,1);

                                          return;

                                   }                  

                                   break;

                            default:

                                   break;

                            }

                     }

              }

              else

              {

                     Sleep(20);

              }

       }

}

前面已经介绍过了,不建议线程函数里用SendMessage给主线程发消息,因为这个函数是同步操作,就是如果SendMessage函数不执行完,是不会返回的,这样线程函数就无法继续执行。

有时这种操作容易导致工作线程和主线程死锁,这个我们后面会谈到,会介绍一种解决方法。

 

三 线程的退出

线程的退出有多种方式,比如可以调用TerminateThread()函数强制线程退出,但不推荐这种方式,因为这样做会导致线程中的资源来不及释放。

最好的也是推荐的方式,是让线程函数自己退出。

就像上面介绍的SetProgress()函数中,用全局变量g_exitThread使线程退出。

而AnalyseProc用WAIT_OBJECT_0==WaitForSingleObject(m_eventExitAnalyse.m_hThread,0)这种方式来退出线程,还有在SetSlider函数中利用发送自定义消息WM_QUIT_THREAD的方式令线程退出。

这些都是可以使用的方法。

当主线程要退出时,为了能保证线程的资源能全部地释放,主线程必须等待工作线程退出。

线程对象和进程对象一样,也是内核对象,而且线程对象的特点是当线程退出时,线程内核对象会自动变为有信号状态,能够唤醒所有正在等待它的线程。

我们通常都习惯于使用WaitForSingleObject等函数来等待某个内核对象变为有信号状态,但是我想说的是,在主线程中不要使用WaitForSingleObject和WaitForMultipleObjects两个函数等待线程退出,其原因就是有导致程序死锁的隐患,特别是线程函数里调用了SendMessage或是直接操作了MFC对象,更易出现此种现象。

下面的函数是一个在主线程中用来等待SetProgress()线程函数退出的函数:

 

//退出线程

voidCThreadTest:

:

OnButton2()

{

       g_exitThread=TRUE; //设置全局变量为真,令线程退出

 

#if1

       WaitForSingleObject(m_pThread1->m_hThread,INFINITE);//无限等待

#else

       DWORDdRet;

       MSGmsg;

       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这组预编译指令控制函数的执行代码,如果我令#if1,则执行WaitForSingleObject函数,如果我令#if0,则执行DWORDdRet路径。

首先令#if 1,测试会发现,程序死锁了。

原因是当程序执行到WaitForSingleObject函数时,主线程挂起,等待线程函数退出,此时CPU切换到线程函数体内执行,如果执行到if(g_exitThread)处,则线程函数顺利退出,可如果执行到m_progress.SetPos(nCount)处,由于SetPos函数是在主线程中完成的操作,Windows是基于消息的操作系统,很多操作都是靠发消息完成的,由于主线程已经挂起,所以没有机会去消息队列中抓取消息并处理它,结果导致SetPos函数不会返回,工作线程也被挂起,典型的死锁。

如果不用m_progress.SetPos,而改用this->SendMessage(…),其结果是一样的。

此时如果用了PostMessage,则工作线程会顺利退出,因为PostMessage是异步执行的。

由此可见,在主线程中用WaitForSingleObject等待工作线程退出是有很大隐患的。

       为解决这一问题,微软特提供了一个MsgWaitForMultipleObjects函数,该函数的特点是它不但可以等待内核对象,还可以等消息。

也就是当有消息到来时,该函数也一样可以返回,并处理消息,这样就给了工作线程退出的机会。

DWORDMsgWaitForMultipleObjects(

DWORDnCount,//要等待的内核对象数目

LPHANDLEpHandles,//要等待的内核对象句柄数组指针

BOOLfWaitAll,//是等待全部对象还是单个对象

DWORDdwMilliseconds,//等待时间

DWORDdwWakeMask);//等待的消息类型

 

下面就详解一下该函数的参数使用方法:

DWORDnCount:

要等待的内核对象的数目。

如果等待两个线程退出,则nCount=2;

LPHANDLEpHandles:

要等待的内核对象句柄数组指针。

 

如果只要等待一个线程退出,则直接设置该线程句柄的指针即可:

MsgWaitForMultipleObjects(1,&m_pThread->m_hThread,…)

 

如果要等待两个线程退出,则使用方法为:

HANDLEhArray[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。

此宏表示的是可以等待任意类型的消息。

当然,也可以指定等待的消息类型。

 

#defineQS_ALLINPUT       (QS_INPUT        |\

                           QS_POSTMESSAGE  |\

                           QS_TIMER        |\

                           QS_PAINT        |\

                           QS_HOTKEY       |\

                           QS_SENDMESSAGE)

 

返回值:

DWORDdRet 通过函数返回值,可以得到一些有效信息。

函数返回值依fWaitAll设置的不同而有所不同。

下面是函数返回值的几种常见类型:

dRet= 0xFFFFFF

展开阅读全文
相关资源
猜你喜欢
相关搜索

当前位置:首页 > 外语学习 > 其它语言学习

copyright@ 2008-2022 冰豆网网站版权所有

经营许可证编号:鄂ICP备2022015515号-1