MFC多线程编程.docx
- 文档编号:25169142
- 上传时间:2023-06-05
- 格式:DOCX
- 页数:30
- 大小:26.69KB
MFC多线程编程.docx
《MFC多线程编程.docx》由会员分享,可在线阅读,更多相关《MFC多线程编程.docx(30页珍藏版)》请在冰豆网上搜索。
MFC多线程编程
MFC多线程编程
1.线程的基本概念
进程和线程都是操作系统的概念。
进程是应用程序的执行实例。
每个进程是由私有的虚拟地址空间、代码、数据和其它系统资源组成的。
进程在运行过程中创建的资源随着进程的终止而被销毁,所使用的系统资源在进程终止时被释放或关闭。
线程是进程内部的一个执行单元。
系统创建进程后,实际上就是启动了该进程的主执行线程,它以函数地址的形式(比如main或WinMain函数),将程序的启动点提供给Windows系统。
主执行线程终止了,进程也就随之终止。
每个进程至少有一个主执行线程,它无需由用户主动创建,而是由系统自动创建的。
用户根据需要在应用程序中创建其它线程,多个线程并发地运行于同一个进程中。
一个进程中的所有线程都在该进程的虚拟地址空间中,共同使用这些虚拟地址空间、全局变量和系统资源,所以线程间的通讯非常方便,多线程技术的应用也较为广泛。
多线程可以实现并行处理,避免了某项任务长时间占用CPU。
单CPU运行多线程时,操作系统为每个线程安排一些CPU时间,轮换方式向线程提供时间片,这就给人一种假象,似乎这些线程在同时运行。
由此可见,如果两个非常活跃的线程为了抢夺CPU的控制权,在线程切换时会消耗很多CPU资源,反而会降低系统性能。
这点要在编程时注意。
创建线程共三种方法:
(1)WIN32API的CreateThread()
(2)MFC全局函数AfxBeginThread()
(3)CWinThread:
:
CreateThread()
2.WIN32API多线程
2.1WIN32API多线程函数
1.HANDLECreateThread(//方法一
LPSECURITY_ATTRIBUTESlpThreadAttributes,
DWORDdwStackSize,
LPTHREAD_START_ROUTINElpStartAddress,
LPVOIDlpParameter,
DWORDdwCreationFlags,
LPDWORDlpThreadId);
2.DWORDSuspendThread(HANDLEhThread);//挂起指定的线程
3.DWORDResumeThread(HANDLEhThread);//结束线程的挂起状态,执行线程
4.VOIDExitThread(DWORDdwExitCode);//主要在线程的执行函数中被调用,终结线程自身的执行
5.BOOLTerminateThread(HANDLEhThread,DWORDdwExitCode);//强行终止线程,一般不建议使用:
(1)是不安全的,可能会引起系统不稳定;
(2)虽然该函数立即终止线程,但并不释放线程所占用的资源。
6.BOOLPostThreadMessage(
DWORDidThread,
UINTMsg,
WPARAMwParam,
LPARAMlParam);//将一条消息放到指定线程的消息队列中,并且不等到消息被该线程处理时便返回。
如果接收消息的线程没有创建消息循环,则该函数执行失败。
7.DWORDWaitForSingleObject(HANDLEhHandle,DWORDdwMilliseconds);//功能:
在某一线程(创建子线程的线程)中调用,线程暂时挂起,系统监视hHandle(子线程句柄)所指向的对象的状态。
返回:
如果在挂起的dwMilliseconds毫秒内,线程所等待的对象变为有信号状态,则该函数立即返回;如果超时时间已经到达dwMilliseconds毫秒,但hHandle所指向的对象还没有变成有信号状态,函数照样返回。
参数dwMilliseconds有两个特殊值:
0和INFINITE。
若为0,立即返回;若为INFINITE,则线程一直被挂起,直到hHandle所指向的对象变为有信号状态。
2.2例程
MultiThread1
使用Win32API编写多线程。
1.建立基于对话框的工程,在对话框中加入两个按钮和一个编辑框,两个按钮的ID分别是IDC_START、IDC_STOP,标题分别为“启动”,“停止”,IDC_STOP的属性选中Disabled;编辑框IDC_TIME的属性选中Readonly;
2.在MultiThread1Dlg.h中添加线程函数声明voidThreadFunc();(应在类CMultiThread1Dlg声明外部)
3.在类CMultiThread1Dlg内部添加protected型变量:
HANDLEhThread;//线程句柄
DWORDThreadID;//线程ID
4.在MultiThread1Dlg.cpp文件中添加全局变量m_bRun:
volatileBOOLm_bRun;//代表线程是否正在运行
volatile修饰符:
告诉编译器无需对该变量作任何的优化,即无需将它放到一个寄存器中,并且该值可被外部改变。
对于多线程引用的全局变量来说,volatile是一个非常重要的修饰符;
5.编写代码:
IDC_START的消息函数:
voidCMultiThread1Dlg:
:
OnStart(){
//TODO:
Addyourcontrolnotificationhandlercodehere
hThread=CreateThread(
NULL,
0,
(LPTHREAD_START_ROUTINE)ThreadFunc,
NULL,
0,
&ThreadID);
GetDlgItem(IDC_START)->EnableWindow(FALSE);
GetDlgItem(IDC_STOP)->EnableWindow(TRUE);
}
IDC_STOP的消息函数:
voidCMultiThread1Dlg:
:
OnStop(){
//TODO:
Addyourcontrolnotificationhandlercodehere
m_bRun=FALSE;
GetDlgItem(IDC_START)->EnableWindow(TRUE);
GetDlgItem(IDC_STOP)->EnableWindow(FALSE);
}
线程函数:
voidThreadFunc(){
CTimetime;
CStringstrTime;
m_bRun=TRUE;//m_bRun==TRUE,线程一直运行
while(m_bRun){
time=CTime:
:
GetCurrentTime();
strTime=time.Format("%H:
%M:
%S");
:
:
SetDlgItemText(:
:
FindWindow(NULL,L"MultiThread1"),IDC_TIME,strTime);
//:
:
SetDlgItemText(AfxGetApp()->m_pMainWnd->m_hWnd,IDC_TIME,strTime);//也能满足要求
//:
:
SetDlgItemText(AfxGetMainWnd()->m_hWnd,IDC_TIME,strTime);//VS2010中画线代码不能获取窗口句柄!
Sleep(1000);
}
}
MultiThread2
(1)如何将一个整型参数传送到一个线程;
(2)如何等待一个线程完成处理。
1.建立一个基于对话框的工程,在对话框中加入一个编辑框和一个按钮,ID分别是IDC_COUNT,IDC_START,按钮控件的标题为“开始”;
2.在MultiThread2Dlg.h中添加线程函数声明:
voidThreadFunc(intinteger);(在类CMultiThread2Dlg声明外部)
3.在类CMultiThread2Dlg内部添加protected型变量:
HANDLEhThread;//线程句柄
DWORDThreadID;//线程ID
4.利用ClassWizard为编辑框IDC_COUNT添加intm_nCount;
5.编写代码:
IDC_START的消息函数:
voidCMultiThread2Dlg:
:
OnStart(){
UpdateData(TRUE);
intinteger=m_nCount;
hThread=CreateThread(
NULL,
0,
(LPTHREAD_START_ROUTINE)ThreadFunc,
(VOID*)integer,
0,
&ThreadID);
GetDlgItem(IDC_START)->EnableWindow(FALSE);
WaitForSingleObject(hThread,INFINITE);
GetDlgItem(IDC_START)->EnableWindow(TRUE);
}
线程函数:
voidThreadFunc(intinteger){
inti;
for(i=0;i Beep(200,50); Sleep(1000); } } MultiThread3 如何将一个结构体指针传送给子线程。 1.建立一个基于对话框的工程,在对话框中加入一个编辑框IDC_MILLISECOND,一个按钮IDC_START,标题为“开始”,一个进度条IDC_PROGRESS1; 2.为IDC_MILLISECOND添加intm_nMilliSecond,为IDC_PROGRESS1添加CProgressCtrlm_ctrlProgress; 3.在MultiThread3Dlg.h中添加一个结构体定义: (在类CMultiThread3Dlg声明外部) structthreadInfo{ UINTnMilliSecond; CProgressCtrl*pctrlProgress; }; 4.在MultiThread3Dlg.h文件中添加线程函数声明: UINTThreadFunc(LPVOIDlpParam);(在类CMultiThread3Dlg声明外部) 5.在类CMultiThread3Dlg内部添加protected型变量: HANDLEhThread; DWORDThreadID; 6.在MultiThread3Dlg.cpp文件中定义公共变量threadInfoInfo; 7.编写代码: IDC_START的消息函数: voidCMultiThread3Dlg: : OnStart(){ //TODO: Addyourcontrolnotificationhandlercodehere UpdateData(TRUE); Info.nMilliSecond=m_nMilliSecond; Info.pctrlProgress=&m_ctrlProgress; hThread=CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)ThreadFunc, &Info, 0, &ThreadID); /*GetDlgItem(IDC_START)->EnableWindow(FALSE); WaitForSingleObject(hThread,INFINITE); GetDlgItem(IDC_START)->EnableWindow(TRUE); */ /*如果执行上面/**/语句,编译运行,程序停止反应,这是因为线程死锁。 因为WaitForSingleObject函数会将主线程挂起(任何消息都得不到处理),而子线程ThreadFunc正在设置进度条,它等主线程将进度条刷新的消息处理完后才会检测通知事件,这样两个线程都在互相等待,死锁发生了,编程时应注意避免。 */ } 在BOOLCMultiThread3Dlg: : OnInitDialog()中添加代码: { …… //TODO: Addextrainitializationhere m_ctrlProgress.SetRange(0,99); m_nMilliSecond=10; UpdateData(FALSE); returnTRUE;//returnTRUEunlessyousetthefocustoacontrol } 线程函数: UINTThreadFunc(LPVOIDlpParam){ threadInfo*pInfo=(threadInfo*)lpParam; for(inti=0;i<100;i++){ intnTemp=pInfo->nMilliSecond; pInfo->pctrlProgress->SetPos(i); Sleep(nTemp); } return0; } MultiThread4 测试在Windows下最多可创建线程的数目。 1.建立一个基于对话框的工程,在对话框中加入一个按钮IDC_TEST,标题为“测试”,一个编辑框IDC_COUNT,属性选中Read-only; 2.在MultiThread4Dlg.cpp中添加公共变量volatileBOOLm_bRunFlag=TRUE;//表示是否还能继续创建线程 3.为IDC_COUNT添加intm_nCount; 4.在MultiThread4Dlg.h中添加: DWORDWINAPIthreadFunc(LPVOIDthreadNum);(在类CMultiThread4Dlg外部) 5.编写代码: IDC_TEST的消息函数: voidCMultiThread4Dlg: : OnTest(){ DWORDthreadID; GetDlgItem(IDC_TEST)->EnableWindow(FALSE); longnCount=0; while(m_bRunFlag){//不断创建线程,直到再不能创建为止 if(NULL==CreateThread(NULL,0,threadFunc,NULL,0,&threadID)){ m_bRunFlag=FALSE; break; } else{ nCount++; } } m_nCount=nCount; UpdateData(FALSE); Sleep(5000);//延时5秒,等待所有创建的线程结束 GetDlgItem(IDC_TEST)->EnableWindow(TRUE); m_bRunFlag=TRUE; } 线程函数: DWORDWINAPIthreadFunc(LPVOIDthreadNum){ while(m_bRunFlag){ Sleep(3000); } return0; } 3.MFC多线程 3.1基本概念 MFC中有两类线程: (1)工作者线程; (2)用户界面线程。 二者主要区别: (1)没有消息循环, (2)有自己的消息队列和消息循环。 (1)通常用来执行后台计算和维护任务,如冗长的计算,后台打印机打印等。 (2)一般用于处理独立于其他线程之外的用户输入,响应用户及系统所产生的事件和消息等。 3.1.1MFC中创建线程 1MFC全局函数AfxBeginThread(): 它有两种重载形式,分别用于创建工作者线程和用户界面线程: //方法二 (1)CWinThread*AfxBeginThread( AFX_THREADPROCpfnThreadProc,//工作者线程函数指针UINTExecutingFunction(LPVOIDpParam);其返回值为0 LPVOIDpParam, nPriority=THREAD_PRIORITY_NORMAL, UINTnStackSize=0, DWORDdwCreateFlags=0, LPSECURITY_ATTRIBUTESlpSecurityAttrs=NULL); (2)CWinThread*AfxBeginThread( CRuntimeClass*pThreadClass, intnPriority=THREAD_PRIORITY_NORMAL, UINTnStackSize=0, DWORDdwCreateFlags=0, LPSECURITY_ATTRIBUTESlpSecurityAttrs=NULL); 2CWinThread类: (1)创建CWinThread类的一个对象, (2)调用CWinThread: : CreateThread()启动线程: //方法三 (1)BOOLCWinThread: : CreateThread( DWORDdwCreateFlags=0, UINTnStackSize=0, LPSECURITY_ATTRIBUTESlpSecurityAttrs=NULL); (2)virtualBOOLCWinThread: : InitInstance();//用于用户界面线程的初始化;工作者线程一般不用 (3)virtualintCWinThread: : ExitInstance();//线程终结前进行一些清理工作;通常用于用户界面线程 CWinThread类的用法: A.创建用户界面线程的步骤: 1.创建类CWinThread的派生类(以CUIThread类为例) 2.重载InitInstance()与ExitInstance(): BOOLCUIThread: : InitInstance(){ CFrameWnd*wnd=newCFrameWnd; wnd->Create(NULL,"UIThreadWindow"); wnd->ShowWindow(SW_SHOW); wnd->UpdateWindow(); m_pMainWnd=wnd; returnTRUE; } 3.创建用户界面线程: voidCUIThreadDlg: : OnButton1(){ CUIThread*pThread=newCUIThread(); pThread->CreateThread(); } 用户界面线程的执行次序与应用程序主线程相同: a)调用用户界面线程类的InitInstance(); b)若InitInstance()返回TRUE,则调用Run(),运行一个标准的消息循环: 线程空闲时调用OnIdle(); 收到WM_QUIT消息中断线程。 c)Run()返回时,MFC调用ExitInstance()清理资源。 B.创建没有界面而有消息循环的线程: (这种方法可以完成一个工作者线程的功能) 1.从CWinThread派生一个新类; 2.在InitInstance()中不创建界面并最后返回FALSE,这表示仅执行InitInstance()中的任务而不执行消息循环。 3.1.2线程间通信 线程通信: 两个线程之间信息传递的渠道。 1.全局变量 由于同一进程的各个线程共享该进程的资源,故线程通信最简单的方法是使用全局变量。 对于标准类型的全局变量,建议使用volatile修饰符,它告诉编译器无需对变量作任何优化: 无需将它放到寄存器中,且该值可被外部改变。 如果线程间传递的信息较复杂,可以定义一个结构体,通过结构体的指针进行信息传递。 2.自定义消息 可以在线程函数中向另一线程发送自定义消息来进行线程通信。 一个线程向另一线程发送消息是通过操作系统实现的。 Windows的消息驱动机制: 当一个线程发出一条消息时,Windows首先接收到该消息,然后把该消息转发给目标线程,目标线程必须已经建立了消息循环。 3.1.3线程的同步 线程的同步: 使隶属于同一进程的各个线程协调一致地工作。 为什么要进行线程的同步? 虽然多线程会带来诸多好处,但也有问题需要解决: 例如,磁盘驱动器是独占性的系统资源,由于线程可以执行进程的任何代码段,且线程的运行是由系统自动调度完成的,具有一定的不确定性,因此就可能出现两个线程同时对磁盘驱动器进行操作的错误。 MFC主要提供的四种线程同步对象: A.临界区(CCriticalSection) B.互斥量(CMutex) C.事件(CEvent) D.信号量(CSemaphore) 3.1.3.1CCriticalSection 某一时刻只有一个线程可以拥有CriticalSection对象,该线程可以访问被保护起来的资源或代码段,其他希望进入CriticalSection的线程被挂起,直到该线程放弃CriticalSection为止。 这样就保证了在同一时刻不会出现多个线程访问共享资源的情况。 其用法如下: 1.定义CCriticalSection类的一个全局对象(以使各个线程均能访问); 2.在需要保护的资源或代码之前,调用CCriticalSection: : Lock()获得临界区对象; 3.访问临界区完毕后,调用CCriticalSection: : Unlock()来释放临界区。 3.1.3.2CMutex Mutex对象与CriticalSection对象很相似,其不同在于: 前者可以在进程间使用,而后者只能在同一进程的各线程间使用,但它比前者更节省系统资源,更有效率。 3.1.3.3CEvent CEvent支持事件。 事件: 是一种同步对象,它在一个线程发生某种情况时,唤醒另一线程。 例如: 在某些网络Apps中,一个线程A负责监听通讯端口,另一线程B负责更新用户数据。 通过使用CEvent类,A可以通知B何时更新用户数据。 CEvent对象有两种状态: 有信号和无信号。 线程监视其CEvent对象的状态,并在相应的时候采取相应的操作。 MFC中CEvent有两种类型: 自动事件和人工事件。 CEvent类默认创建的是前者,它被至少一个线程释放后,自动返回无信号;后者需要调用ReSetEvent()才能设置为无信号。 CEvent成员函数: 1.CEvent( BOOLbInitiallyOwn=FALSE,//指定事件对象初始化状态: TRUE为有信号 BOOLbManualReset=FALSE,//指定要创建的是人工事件还是自动事件: TRUE为人工事件 LPCTSTRlpszName=NULL,//一般为NULL LPSECURITY_ATTRIBUTESlpsaAttribute=NULL);//一般为NULL 2.BOOLCEvent: : SetEvent(); //对于人工事件,它将CEvent对象保持为有信号,直到调用ResetEvent()为止; //对于自动事件,它将CEvent对象设置为有信号,CEvent对象由系统自动重置为无信号。 3.BOOLCEvent: : ResetEvent(); //它将事件设置为无信号,并保持该状态直至调用SetEvent()为止。 //自动事件不需要调用它。 //一般通过WaitForSingleObject()监视事件状态。 3.1.3.4CSemaphore CSemaphore提供了访问共享资源线程的计数,它能限制访问共享资源线程的数目。 CSemaphore构造函数: CSemaphore( LONG
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- MFC 多线程 编程