在MFC程序中使用AfxBeginThread函数来创建一个线程,该函数因参数不同而具有两种重载函数,分别对应工作者线程和用户接口(UI)线程。
一、工作线程
1、创建线程MFC API函数 CWinThred* AfxBeginThread( AFX_THREADPROC pfnThreadProc, //线程函数 LPVOID pParam,//传递给控制函数的参数 int nPriorty = THREAD_PRIORITY_NORMAL,//线程的优先级 UNIT nStackSize = 0,//线程的创 建标志 LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL//线程的安全属性 );
2、线程工作函数
UNIT MfcThreadProc(LPVOID lpParam) { //对指针进行强制转型,对指针所指内容进行解码 CExampleClass* lpObject = (CExampleClass*)lpParam; if (lpObject == NULL||!lpObject->IsKindof(RUNTIME_CLASS(CExampleClass))) { return -1; } while(1) { ../ } return 0; }
3、使用例 m_pThread = AfxBeginThread(NewThreadTest,&wenli); m_pThread->m_pAutoDelete = TRUE; m_pThread->ResumeThread(); m_bAutoDelete = TRUE; 系统自己清理CWind对象,当然还包括CloseHandle(),ExitInstance()等等一堆函数的调用。 m_bAutoDelete = FALSE; 自己在用完后调用delete()删除创建线程的对象,否则会有内存泄漏问题。
二、UI线程
UI线程是由CWinThread派生控制的,这个派生类和CWinApp 极为类似,实际上CWinApp 也是一个UI线程,他是应用程序的主线程 , 一般我们所说的UI 线程,是指除主线程之外的界面线程。
1、创建用户界面线程时,必须首先从CWinThread派生类;
2、使用DECLARE_DYNCREATE和IMPLEMENT_DYNCREATE以建立类表;
3、使用VS提供的增加MFC类向导可以轻松完成类的添加工作,类添加后需要对其进行必要的修改。
需要重写如下两个成员函数。 //执行线程实例初始化,必须重写 virtualBOOL InitInstance();
//线程终止时执行清除,通常需要重写 virtualint ExitInstance(); // default will \'delete this\'
4、CWinThread* AfxBeginThread( CRuntimeClass* pThreadClass,//从CWinThread派生的类的RUNTIME_CLASS int nPriorty = THREAD_PRIORITY_NORMAL, UNIT nStackSize = 0; DWORD dwCreateFlags = 0; LPSECURITY_ATTRIBUTES lpSecurityAttrs = NULL );
5、启动线程 设置CWinThread类的m_pMainWnd成员,否则这个线程不会随着界面的关闭而退出; CWinThread* pThread = AfxBeginThread(RUNTIME_CLASS(CmyUIClassName));
6、结束线程 ①UI线程有消息队列,所以结束一个UI线程最好的方法是发一个WM_QUIT消息给消息队列。 PosiQuitMessage() PostThreadMessage() 但是发出消息后最好等待看Ui线程是否已经退出(很重要)
if (pThread)
{ //发一个WM_QUIT 消息给UI线程
pThread->PostThreadMessage(WM_QUIT,NULL,NULL); //等待一个UI线程正常退出
if (WAIT_OBJECT_0 == WaitForSingleObject(pThread->m_pThread,INFINTE))
{ //删除UI线程对象,只有当你设置了m_bAutoDelete = FALSE时才调用
delete pThread;
}
}
②普通的工作线程收到返回值既表示正确退出
三、线程间的通信
1、全局变量方式
2、控件指针或其它全局参数传递方式 AfxBeginThread(NewThreadTest,&wenli);
3、消息传递方式
A、工作线程可以使用:PostThreadMessage(); 消息映射为ON_THREAD_MESSAGE而不是ON_MESSAGE
B、UI线程可以使用 异步函数:PostMessage(); 同步函数:SendMessage();
C、使用方法 #difine WM_THREADMSG WMUSER+100
全局函数:PostThreadMessage(idThread,WM_THREADMSG,parm1,parm2)
或者使用类成员函数 m_pThread->PostThreadMessage(WM_THREADMSG,parm1,parm2);
四、线程同步
各个线程可以访问进程中的公共变量,资源,所以使用多线程的过程中需要注意的问题是如何防止两个或两个以上的线程同时访问同一个数据, 以免破坏数据的完整性,数据之间的互相制约包括 1、直接制约关系,即一个线程的处理结果,为另一个线程的输入,因此线程之间直接制约着,这种关系可以称为同步关系 2、间接制约关系,即两个线程需要访问同一资源,该资源在同一时刻只能被一个线程访问,这种关系称之为线程间对资源的互斥访问,某种意义上说互 斥是一种制约关系更小的同步。
A、临界区(CCriticalSection) 当多个线程访问一个独占性共享资源时,可以使用临界区对象。拥有临界区的线程可以访问被保护起来的资源或代码段,其他线程若想访问,则被挂起, 知道拥有临界区的线程放弃临界区为止;
具体应用方式:
1、定义临界区对象CCriticalSection g_CriticalSection;
2、在访问共享资源(代码或变量)之前,先获得临界区对象,g_CriticalSection.Lock();
3、访问共享资源后,则放弃临界区对象,g_CriticalSection.Unlock();
B、事件(CEvent)
1、 CEvent(BOOLbInitiallyOwn=FALSE,
BOOLbManualReset=FALSE,
LPCTSTRlpszName=NULL,
LPSECURITY_ATTRIBUTES lpsaAttribute=NULL );
bInitiallyOwn:指定事件对象初始化状态,TRUE为有信号,FALSE为无信号;
bManualReset:指定要创建的事件是属于人工事件还是自动事件。
TRUE为人工事件,FALSE为自动事件;
后两个参数一般设为NULL,在此不作过多说明。
2、BOOLCEvent::SetEvent(); 将CEvent类对象的状态设置为有信号状态,如果事件是人工事件,则CEvent类对象保持有信号状态,直到调用成员函数ResetEvent()将其重新设为无信号状态是为止。如果CEvent类对象为自动事件,则在SetEvent()将事件设置为有信号状态后,CEvent类对象有系统自动充值为无信号状态。 如果该函数执行成功,则返回非零值,否则返回零。
3、BOOLCEvent::ResetEvent(); 该函数将时间的状态设置为无信号状态,并保持改状态直至SetEvent()被调用时为止,由于自动事件有系统自动充值,故自动事件不需要调用该函数, 如果该函数执行成功,返回非零值,否则返回零,我们一般通过调用WaitForSingleObject函数来监视事件状态;
C、互斥量(CMutex) 互斥对象与临界区对象很像,互斥对象与临界区对象的不同在于:互斥对象可以在进程间使用,而临界区对象只能在同一进程的各线程间使用,当然, 互斥对象也可以用于同一进程的各个线程间,但是在这种情况下,使用临界区会更节省系统资源,更有效率。 D、信号量(CSemphore) 当需要一个计数器来限制可以使用某个线程的数目时,可以使用“信号量”对象。CSemaphore类的对象保存了对当前访问某一指定的线程的计数值, 该计数值是当前还可以使用该资源的线程的数目。如果这个计数达到了零,则所有对这个CSemaphore类对象所控制的资源的访问尝试都被放入到一个 队列中等待,直到超时或计数值不为零时为止。