1、-1-本章目标 了解进程和线程的区别 掌握多线程程序的应用场合 掌握利用API创建线程的方法 掌握利用MFC创建界面线程和工作者线程的方法 掌握使用全局变量和自定义消息实现线程通信的方法 掌握使用CEvent类、CCriticalSection类、CSemaphore类实现线程同步的方法多线程内核对象 内核对象:p内核对象只是一个内存块,它由操作系统内核分配,并只能由操作系统内核访问p内核对象是一个数据结构,其成员维护者与对象相关的信息,少数成员(安全描述符合使用计数等)是所有对象都有的,但其他大多数成员都是不同类型的对象特有的p利用Windows提供的一组函数操纵内核对象p调用一个创建内核对
2、象的函数后,函数会返回一个句柄(handle),它标识了所创建的对象,在32为的操作系统中,句柄是一个32位值;在64位Windows进程中,则是一个64位值p一个进程在初始化时,系统将为它分配一个句柄表,这个句柄表仅供内核对象使用,不适用用户对象和GDI对象-2-多线程编程基础进程和线程 进程:一个正在运行的程序的一个实例,由两部分构成p一个内核对象,操作系统用它来管理进程。内核对象也是系统保存进程统计信息的地方;p一个地址空间,其中包含所有可执行文件(executable)或Dll模块的代码和数据。此外,它还包含动态内存分配,比如线程堆栈和堆的分配。进程和线程的关系进程是有“惰性”的,进程
3、要做任何事情,都必须让一个线程在它的上下文中运行。p一个进程可以有多个线程,所有线程都在进程的地址空间中“同时”执行代码p每个线程都有他自己的一组CPU寄存器和它自己的堆栈p当系统创建一个进程的时候,会自动为进程创建第一个线程,这称为主线程(primary thread),主线程负责创建更多的线程-3-多线程编程基础进程和线程 进程:一个正在运行的程序的一个实例,由两部分构成p 进程是有“惰性”的,进程要做任何事情,都必须让一个线程在它的上下文中运行。p 一个进程可以有多个线程,所有线程都在进程的地址空间中“同时”执行代码p 每个线程都有他自己的一组CPU寄存器和它自己的堆栈p 当系统创建一个
4、进程的时候,会自动为进程创建第一个线程,这称为主线程(primary thread),主线程负责创建更多的线程-4-多线程编程基础进程和线程 线程是进程内部的一个执行单元,它是操作系统分配处理器时间的基本单位 从程序员的角度看,进程即一个可执行程序;线程是进程内的一个可以被操作系统独立调度执行的函数 操作系统会轮流为每个线程调度一些CPU时间。它会采取循环(round_robin,轮询或轮流)方式,为每个线程都分配时间片(称为“量”或者“量程”,即quantum)-5-多线程编程基础进程地址空间 进程:系统赋予每个进程独立的虚拟地址空间 每个进程有它自己的私有地址空间 4GB是虚拟的地址空间,
5、只是内存地址的一个范围 4GB虚拟地址空间中,2GB是内核方式分区,供内核代码、设备驱动程序、设备I/O高速缓冲、非页面内存池的分配和进程页面表等使用,而用户方式分区使用的地址空间约为2GB,这个分区是进程的私有地址空间所在的地方。-6-多线程编程基础线程 进程:线程实质上是程序中的执行路径。也是Win32安排的最小执行单元,线程包括堆栈、CPU寄存器的状态和系统计划程序执行列表中的项p 线程的内核对象,操作系统用它来对线程实施管理。内核对象也是系统用来存放线程统计信息的地方。p 线程堆栈,它用于维护线程在执行代码时需要的所有参数和局部变量。-7-多线程编程基础跨进程边界共享内核对象 不同进程
6、中运行的线程需要共享内核对象:p 利用文件映射对象,可以在同一台机器上运行的两个不同进程之间共享数据块;p 借助邮件槽和命名管道,在网络中的不同计算机上运行的进程可以相互发送数据块;p 互斥量、信号量和事件允许不同进程中的线程同步执行。-8-多线程编程基础使用多线程的意义 像网络通信程序这样既要进行耗时的工作(远程数据收发),又要保持对用户输入(键盘和鼠标)响应,使用多线程是最佳选择。一些大型服务程序,如数据库系统,网络游戏服务器程序,使用多线程将赋予应用程序强大的控制能力和执行能力。-9-多线程编程API多线程编程函数名函数名功能描述功能描述CreateThread()创建一个新的线程,返回
7、已创建线程的句柄SuspendThread()挂起指定线程,暂停线程的执行ResumThread()将线程的挂起状态计数值减1,该值减到0时,线程恢复执行ExitThread()由线程自身完成终止执行操作TerminateThread()强行终止某线程的操作GetThreadPriority()获取一个线程的优先级GetExitCodeThread()获取线程的退出代码SetThreadPriority()设定一个线程在所在进程中的相对优先级PostThreadMessage()用于向线程发送消息,即消息放入到指定的消息队列中,并立即返回 与线程操作有关的API函数-10-多线程编程API多线
8、程编程 创建线程#include/定义线程函数DWORD WINAPI ThreadFun(LPVOID lpParameter).return 0;int main(int argc,char*argv)/创建线程CreateThread(NULL,0,ThreadFun,0,0,0);.-11-多线程编程MFC界面线程 MFC的界面线程其实就是拥有消息循环和窗体的线程,当程序中需要多个窗口,而有的窗口需要包含“费时”的操作,这时需要创建界面线程。MFC创建界面线程需要用到CWinThread类。-12-多线程编程MFC界面线程 CWinThread类成员函数成员函数功能描述功能描述Init
9、Instance()在派生类中重写该函数以控制用户界面线程实例的初始化。ExitInstance()在派生类中重写该函数以在线程终结前进行一些必要的清理工作,例如释放动态分配的内存,关闭图形设备对象和文件句柄等。CreateThread()创建线程,作用同API函数CreateThread()SetThreadPriority()设置当前线程的优先级,作用同API函数SetThreadPriority()GetThreadPriority()获取当前线程的优先级,作用同API函数GetThreadPriority()SuspendThread()增加当前线程的挂起计数,作用同API函数Susp
10、endThread()ResumeThread()减少当前线程的挂起计数,作用同API函数ResumeThreadPostThreadMessage()给当前线程投递线程消息,作用同API函数PostThreadMessage()-13-多线程编程MFC界面线程 用户界面线程的创建过程:从CWinThread派生自定义类;在类定义文件中添加DECLARE_DYNCREATE宏,在类实现文件中添加IMPLEMENT_DYNCREATE宏,这两个宏配合使用,以实现类的动态创建对象功能;在派生类中重写InitInstance()函数,在函数中创建用户界面窗口,并将窗口指针赋值m_pMainWnd,该
11、函数在线程创建时,被自动调用;如果需要,可以在派生类中重写ExitInstance()函数,做一些必要的清理工作,在线程退出前该函数被自动调用。-14-多线程编程MFC界面线程 用户界面线程的启动,有两种方法:p 使用全局函数AfxBeginThread()。p 创建CWinThread对象,而后调用CreateThread()成员函数。-15-多线程编程MFC工作者线程 创建工作者线程时,通常无需从CWinThread派生新的线程类,只需要提供一个线程入口,也就是一个函数地址即可。/线程函数的固定原型是:UINT _cdecl Function(LPVOID pParam);/AfxBegi
12、nThread()函数创建工作者线程的原型如下:CWinThread*AfxBeginThread(AFX_THREADPROC pfnThreadProc,LPVOID pParam,int nPriority=THREAD_PRIORITY_NORMAL,UINT nStackSize=0,DWORD dwCreateFlags=0,LPSECURITY_ATTRIBUTES lpSecurityAttrs=NULL);-16-线程间的通信 有两种方法可以完成线程通信:p使用全局变量p使用自定义消息-17-线程间的通信使用全局变量 由于属于同一个进程的各个线程共享操作系统分配该进程的资源,
13、因此解决线程间通信最简单的一种方法是使用全局变量。方法就是在进程内定义一个全局变量用于线程间的通信处理。-18-线程间的通信使用自定义消息 一个线程可以向另外一个线程发送消息,以实现线程间的通信。其中,接收消息的线程必须是界面线程,因为只有界面线程才有消息循环。发送消息的线程既可以是工作者线程,也可以是界面线程 -19-线程间的通信使用自定义消息 发送消息可以调用以下几个API函数:pSendMessage():向某个窗体发送消息,调用该函数前需要提前获取窗口的句柄。接收消息的窗口处理完消息后,该函数才能返回。pPostMessage():向某个窗体投递消息,调用该函数前需要提前获取窗口的句柄
14、。不管接收消息的窗口是否处理完消息,该函数都立刻返回,即不等待消息处理完毕。pPostThreadMessage():向某个线程发送消息,调用该函数前需要提前获取线程的ID。-20-线程同步MFC线程同步类 同步对象类包括:pCCriticalSection(临界区)pCEvent(事件)pCSemaphore(信号量)pCMutex(互斥)同步访问类包括:pCMultiLockpCSingleLock。-21-线程同步MFC线程同步类 同步对象类的选择方法如下:p若同时只能有一个线程使用此资源,则使用CCriticalSection。p若应用程序必须等到发生某事件才能访问资源(例如,在将数据
15、写入文件之前,必须先从通信端口接收它),则使用CEvent。p若同一应用程序内一个以上的线程可以同时访问资源(例如,应用程序允许在同一文档上最多同时打开五个带有视图的窗口),则使用CSemaphore。-22-线程同步MFC线程同步类 同步访问类的选择方法如下:p如果应用程序只与访问单个受控资源有关,则使用CSingleLock。p如果需要访问多个受控资源中的任何一个,则使用CMultiLock。-23-线程同步CriticalSection类 CriticalSection类提供了对临界区对象的支持,用法如下:p 用CCriticalSection类定义全局对象或在线程执行期间一直可以访问的
16、类数据成员。p 在访问需要保护的资源之前,定义CSingleLock对象,并将上述CCriticalSection对象的地址传递到CSingleLock类的构造函数。p 调用CSingleLock:Lock()函数请求获得临界区。此时,如果临界区已经被其他线程占用,则当前线程挂起,直到拥有临界区的线程释放临界区时为止。p 访问完毕临界区中的资源后,调用CSingleLock:UnLock()函数释放临界区。-24-线程同步CEvent类 CEvent对象的使用方法比较简单p 先创建事件对象p 然后在合适的位置同步等待或改变其信号状态,以实现通知效果。-25-线程同步CEvent类 与CCrit
17、calSection不同,CEvent的同步等待一般不使用CSingleLock类而是要调用API函数 WaitForSingleObject()或WaitForMultipleObjects()-26-UINT CEventDemoDlg:ThreadWrite(LPVOID pParam)CEdit*pEdit=(CEdit*)pParam;/清空显示pEdit-SetWindowText(_T();/事件对象上等待WaitForSingleObject(m_Event.m_hObject,INFINITE);/处理数据CString str=m_pData;str.MakeUpper()
18、;/转换成大写pEdit-SetWindowText(str);/送显/释放内存deletem_pData;return 0;UINT CEventDemoDlg:ThreadRead(LPVOID pParam)CEdit*pEdit=(CEdit*)pParam;TCHAR*pStr=g_Str;/设置信号为无信号状态。当前事件对象创建时无信号,此句可以省略/m_Event.ResetEvent();CString str;while(*pStr)str+=*pStr;/读取字符到str对象中pStr+;pEdit-SetWindowText(str);/送显Sleep(100);/动态分
19、配内存m_pData=new TCHARstr.GetLength()+1;/将字符串复制到内存中_tcscpy(m_pData,str);/数据读取完毕,设置事件对象为有信号状态m_Event.SetEvent();return 0;线程同步CEvent类-27-线程同步CSemaphore类 和CCriticalSection类使用方法相似,CSemaphore类配合CSingleLock类可以进行同步操作。唯一不同的是,CSemaphore类可以控制多个线程对资源的访问。在CCSemaphore类的构造函数中,可以指定对所控制资源同时接受访问的最大线程数。CSemaphore(LONG
20、lInitialCount=1,/信号量对象的初始计数值 LONG lMaxCount=1,/信号量对象计数值的最大值 LPCTSTR pstrName=NULL,LPSECURITY_ATTRIBUTES lpsaAttributes=NULL);-28-小结 进程拥有自己的内存空间和系统资源,进程之间不能直接共享内存 线程是进程内部的一个执行单元,它是操作系统分配处理器时间的基本单位 线程不能单独存在,必须拥有一个所有者进程 一个进程至少包含一个线程,即主线程 线程可以与同一进程中的其他线程共享内存和关联的资源 多线程程序赋予进程强大的并发执行能力 线程函数的结束意味着线程的自然终止 不建议使用ExitThread()和TerminteThread()函数终止线程,线程最好自然终止 MFC中多线程创建方法是对API的包装 在MFC中,线程分为用户界面线程和工作者线程两种 用户界面线程可以和用户交互,拥有自己的消息队列和消息循环来处理界面消息 工作者线程没有消息循环,一般用来完成后台工作-29-谢 谢 Thanks for listening.