
线程函数的设计以及MsgWaitForMultipleObjects函数的使用要点.doc
11页线程函数的设计以及 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;}上面的线程函数用到了三个事件变量 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_QUITPeekMessage 函数也可以从消息队列中抓取消息,如果它的最后一个参数设置为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);}*///方式三 同时使用 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);}}}前面已经介绍过了,不建议线程函数里用 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()线程函数退出的函数://退出线程void CThreadTest::OnButton2(){g_exitThread=TRUE; //设置全局变量为真,令线程退出#if 1WaitForSingleObject(m_pThread1->m_hThread,INFINITE)。












