没有合适的资源?快使用搜索试试~ 我知道了~
C++多线程编程,一份非常完美的资料,教程非常祥细地介绍C++多线程的编程应用,适合作为参考手册使用或更深入学习C++线程。
资源推荐
资源详情
资源评论
一、引言
Windows 系统平台经历了从 16 位到 32 位的转变后,系统运行方式和任务管理方式
有了很大的变化,在 Windows 95 和 Windows NT 中,每个 Win32 程序在独立的进程空间
上运行,32 位地址空间使我们从 16 位段式结构的 64K 段限制中摆脱出来,逻辑上达到了
4G 的线性地址空间。这样,我们在设计程序时就不再需要考虑编译的段模式,同时还提高
了大程序的运行效率。独立进程空间的另一个更大的优越性是大大提高了系统的稳定性,
一个应用程序的异常错误不会影响其它的应用程序,这对于现在的桌面环境尤为重要。r
在 Windows 的一个进程内,包含一个或多个线程。线程是指进程的一条执行路径,它
包含独立的堆栈和 CPU 寄存器状态,每个线程共享所有的进程资源,包括打开的文件、信
号标识及动态分配的内存等等。一个进程内的所有线程使用同一个 32 位地址空间,而这些
线程的执行由系统调度程序控制,调度程序决定哪个线程可执行以及什么时候执行线程。
线程有优先级别,优先权较低的线程必须等到优先权较高的线程执行完任务后再执行。在
多处理器的机器上,调度程序可将多个线程放到不同的处理器上去运行,这样就可使处理
器的任务平衡,也提高了系统的运行效率。r
32 位 Windows 环境下的 Win32 API 提供了多线程应用程序开发所需要的接口函数,
但 Win16 和 Win32 对多线程应用并不支持,利用 Visual C++ 5.0 中提供的标准 C 库也可以
开发多线程应用程序,而相应的 MFC4.21 类库则封装了多线程编程的类,因而用户在开发
时可根据应用程序的需要和特点选择相应的工具。r
如果用户的应用程序需要有多个任务同时进行相应的处理,则使用多线程是较理想的
选择。例如,就网络文件服务功能的应用程序而言,若采用单线程编程方法,则需要循环
检查网络的连接、磁盘驱动器的状况,并在适当的时候显示这些数据,必须等到一遍查询
后才能刷新数据的显示。对使用者来说,延迟可能很长。而在应用多线程的情况下可将这
些任务分给多个线程,一个线程负责检查网络,另一个线程管理磁盘驱动器,还有一个线
程负责显示数据,三个线程结合起来共同完成文件服务,使用者也可以及时看到网络的变
化。多线程应用范围很广,尤其是在目前的桌面平台上,系统的许多功能如网络
(Internet)、打印、字处理、图形图像、动画和文件管理都在一个系统下运行,更需要我们
的应用程序能够同时处理多个事件,而这些正是多线程可以实现的。本文讲述了利用
Visual C++ 5.0 进行多线程开发的编程技术。r
二、基于 Visual C++的多线程编程r
Visual C++ 5.0 提供了 Windows 应用程序的集成开发环境 Developer Studio。在这个
环境里,用户既可以编写 C 风格的 32 位 Win32 应用程序,也可以利用 MFC 类库编写 C++
风格的应用程序,二者各有其优点:基于 Win32 的应用程序执行代码小巧,运行效率高,
但要求程序员编写的代码较多,且需要管理所有系统提供给程序的资源;而基于 MFC 类
库的应用程序可以快速建立起应用程序,类库为程序员提供了大量的封装类,而且
Developer Studio 为程序员提供了一些工具来管理用户源程序,其缺点是类库代码很庞大,
应用程序的执行代码离不开这些代码。由于使用类库所带来的快速、简捷和功能强大等优
越性,因此,除非有特殊的需要,否则 Visual C++提倡使用 MFC 类库进行应用程序开发。
r
多线程的编程在 Win32 方式下和 MFC 类库支持下的原理是一致的,进程的主线程在
任何需要的时候都可以创建新的线程。当线程执行完任务后,自动中止线程;当进程结束
后,所有的线程都中止。所有活动的线程共享进程的资源。因此,在编程时需要考虑在多
个线程访问同一资源时产生冲突的问题:当一个线程正在访问一个进程对象时,另一个线
程要改变该对象,这时可能会产生错误的结果。所以,程序员编程时要解决这种冲突。r
下面给大家介绍一下在 Win32 基础上进行多线程编程的过程。r
1.用 Win32 函数创建和中止线程r
Win32 函数库中提供了多线程控制的操作函数,包括创建线程、中止线程、建立互斥
区等。首先,在应用程序的主线程或者其它活动线程的适当地方创建新的线程。创建线程
的函数如下:r
HANDLE CreateThread(LPSECURITY_ATTRIBUTES lpThreadAttributes, DWORD
dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter,
DWORD dwCreationFlags, LPDWORD lpThreadId );
其中,参数 lpThreadAttributes 指定了线程的安全属性,在 Windows 95 中被忽略;
dwStackSize 指定了线程的堆栈深度;lpStartAddress 指定了线程的起始地址,一般情况
为下面的原型函数:DWORD WINAPI ThreadFunc( LPVOID );lpParameter 指定了线程执
行时传送给线程的 32 位参数,即上面函数的参数;dwCreationFlags 指定了线程创建的特
性; lpThreadId 指向一个 DWORD 变量,可返回线程 ID 值。r
如果创建成功则返回线程的句柄,否则返回 NULL。r
创建了新的线程后,则该线程就开始启动执行了。如果在 dwCreationFlags 中用了
CREATE_SUSPENDED 特性,那么线程并不马上执行,而是先挂起,等到调用
ResumeThread 后才开始启动线程,在这个过程中可以调用函数:r
BOOL SetThreadPriority( HANDLE hThread, int nPriority);
来设置线程的优先权。r
当线程的函数返回后,线程自动中止。如果在线程的执行过程中中止的话,则可调用
函数:r
VOID ExitThread( DWORD dwExitCode);
如果在线程的外面中止线程的话,则可调用下面的函数:r
BOOL TerminateThread( HANDLE hThread, DWORD dwExitCode );
但应注意:该函数可能会引起系统不稳定,而且线程所占用的资源也不释放。因此,
一般情况下,建议不要使用该函数。r
如果要中止的线程是进程内的最后一个线程,则在线程被中止后相应的进程也应中止。
r
2.用 Win32 函数控制线程对共享资源的访问r
在线程体内,如果该线程完全独立,与其它的线程没有数据存取等资源操作上的冲突,
则可按照通常单线程的方法进行编程。但是,在多线程处理时情况常常不是这样,线程之
间经常要同时访问一些资源。例如,一个线程负责公式计算,另一个线程负责结果的显示,
两个线程都要访问同一个结果变量。这时如果不进行冲突控制的话,则很可能显示的是不
正确的结果。r
对共享资源进行访问引起冲突是不可避免的,但我们可用以下办法来进行操作控制:r
(1) 通过设置线程的互斥体对象,在可能冲突的地方进行同步控制。r
首先,建立互斥体对象,得到句柄:r
HANDLE CreateMutex( );
然后,在线程可能冲突区域的开始(即访问共享资源之前),调用
WaitForSingleObject 将句柄传给函数,请求占用互斥体对象:r
dwWaitResult = WaitForSingleObject(hMutex, 5000L);
共享资源访问完后,释放对互斥体对象的占用:r
ReleaseMutex(hMutex);
互斥体对象在同一时刻只能被一个线程占用。当互斥体对象被一个线程占用时,若有
另一线程想占用它,则必须等到前一线程释放后才能成功。r
(2) 设置信号:在操作共享资源前,打开信号;完成操作后,关闭信号。这类似于互斥
体对象的处理。r
首先,创建信号对象:r
HANDLE CreateSemaphore( );
或者打开一个信号对象:r
HANDLE OpenSemaphore( );
然后,在线程的访问共享资源之前调用 WaitForSingleObject。r
共享资源访问完后,释放对信号对象的占用:r
ReleaseSemaphore();
信号对象允许同时对多个线程共享资源的访问,在创建对象时指定最大可同时访问的
线程数。当一个线程申请访问成功后,信号对象中的计数器减一;调用
ReleaseSemaphore 函数后,信号对象中的计数器加一。其中,计数器值大于等于 0,小
于等于创建时指定的最大值。利用信号对象,我们不仅可以控制共享资源的访问,还可以
在应用的初始化时候使用。假定一个应用在创建一个信号对象时,将其计数器的初始值设
为 0,这样就阻塞了其它线程,保护了资源。待初始化完成后,调用 ReleaseSemaphore
函数将其计数器增加至最大值,进行正常的存取访问。r
(3) 利用事件对象的状态,进行线程对共享资源的访问。r
用 ResetEvent 函数设置事件对象状态为不允许线程通过;用 SetEvent 函数设置事件
对象状态为可以允许线程通过。r
事件分为手工释放和自动释放。如果是手工释放,则按照上述两函数处理事件的状态;
如果是自动释放,则在一个线程结束后,自动清除事件状态,允许其它线程通过。r
(4) 设置排斥区。在排斥区中异步执行时,它只能在同一进程的线程之间共享资源处理。
虽然此时上面介绍的三种方法均可使用,但是,使用排斥区的方法则使同步管理的效率更
高;r
先定义一个 CRITICAL_SECTION 结构的排斥区对象,在进程使用之前先对对象进行
初始化,调用如下函数:r
VOID InitializeCriticalSection( LPCRITICAL_SECTION );
当一个线程使用排斥区时,调用函数:r
EnterCriticalSection 或者 TryEnterCriticalSection
当要求占用、退出排斥区时,调用函数:r
LeaveCriticalSection
释放对排斥区对象的占用,供其它线程使用。r
互斥体对象、信号对象和事件对象也可以用于进程间的线程同步操作。在用 Win32 函
数创建了对象时,我们可以指定对象的名字,还可以设置同步对象在子进程的继承性。创
建返回的是 HANDLE 句柄,我们可以用函数 DuplicateHandle 来复制对象句柄,这样每个
进程都可以拥有同一对象的句柄,实现进程之间的线程同步操作。另外,在同一进程内,
我们可以用 OpenMutex、OpenSemaphore 和 OpenEvent 来获得指定名字的同步对象的
句柄。r
排斥区异步执行的线程同步方法只能用于同一进程的线程之间共享资源处理,但是这
种方法的使用效率较高,而且编程也相对简单一些。r
在 Visual C++中,除了利用 Win32 函数进行多线程同步控制外,如果我们用到了
MFC 类库,则可利用已经封装成 C++类结构的同步对象,使我们的编程更加简捷。r
三、基于 MFC 的多线程编程r
在 Visual C++ 5.0 附带的 MFC 4.21 类库中,也提供了多线程编程的支持,基本原理
与上面所讲的基于 Win32 函数的设计一致,但由于 MFC 对同步对象作了封装,因此对用
户编程实现来说更加方便,避免了对象句柄管理上的繁琐工作。更重要的是,在多个窗口
线程情况下,MFC 中直接提供了用户接口线程的设计。r
在 MFC 中,线程分为两种:用户接口线程和辅助线程。用户接口线程常用于接收用
户的输入,处理相应的事件和消息。在用户接口线程中,包含一个消息处理循环,其中
CWinApp 就是一个典型的例子,它从 CWinThread 派生出来,负责处理用户输入产生的事
件和消息。辅助线程常用于任务处理(比如计算)不要求用户输入,对用户而言,它在后
台运行。Win32 API 并不区分这两种线程的类型,它只是获取线程的起始地址,然后开始
执行线程。而 MFC 则针对不同的用户需要作了分类。如果我们需要编写多个有用户接口
的线程的应用程序,则利用 Win32 API 要写很多的框架代码来完成每个线程的消息事件的
处理,而用 MFC 则可以充分发挥 MFC 中类的强大功能,还可以使用 ClassWizard 来帮助
管理类的消息映射和成员变量等,我们就可以把精力集中到应用程序的相关代码编写上。r
辅助线程编程较为简单,设计的思路与上节所讲的基本一致:一个基本函数代表了一
个线程,创建并启动线程后,则线程进入运行状态;如果线程用到共享资源,则需要进行
资源同步处理。共享资源的同步处理在两种线程模式下完全一致。r
我们知道:基于 MFC 的应用程序有一个应用对象,它是 CWinApp 派生类的对象,该
对象代表了应用进程的主线程。当线程执行完(通常是接收到 WM_QUIT 消息)并退出线程
时,由于进程中没有其它线程的存在,故进程也自动结束。类 CWinApp 从 CWinThread
派生出来,CWinThread 是用户接口线程的基本类。我们在编写用户接口线程时,需要从
CWinThread 派生我们自己的线程类,ClassWizard 可以帮助我们完成这个工作。r
下面列出编写用户接口线程的基本步骤。r
1.用 ClassWizard 派生一个新的类,设置基类为 CWinThread
剩余21页未读,继续阅读
资源评论
robert224
- 粉丝: 0
- 资源: 7
上传资源 快速赚钱
- 我的内容管理 展开
- 我的资源 快来上传第一个资源
- 我的收益 登录查看自己的收益
- 我的积分 登录查看自己的积分
- 我的C币 登录后查看C币余额
- 我的收藏
- 我的下载
- 下载帮助
安全验证
文档复制为VIP权益,开通VIP直接复制
信息提交成功