
第 21 卷第 8 期 2001 年 8 月计算机应用 Computer Applications文章编号: 1001- 9081( 2001) 08- 0256- 03如何利用 MFC 实现线程间的同步刘勇 ( 海军潜艇学院 核潜艇系, 山东 青岛 266071)Vol. 21, No. 8 Aug. , 2001摘 要: 利用 MFC 提供的几个线程同步对象类,编写了几个线程安全类。采用这几个线程安全 类可以实现不同情况下的线程同步问题。关键词: MFC; 线程; 类 中图分类号: TP311. 1 文献标识码: A1 引言在进行应用程序设计的 时候ccriticalsection 使用, 常常需 要有多 个任务 同时 进行相应的处理, 此时使用多线程是较理想的 选择。例 如, 就 网络文件服务用途的应用程序而言, 若采取单线程编程 方法, 则必须循环检查网络的联接、磁盘驱动器的情况, 并在适度的 时候显示这种数据, 必须 等到一 遍查询 后能够 刷新数 据的显 示。对使用者来说, 延迟 可能更 长。而在 应用多 线程的 情况 下能将这种任务分给多个线程, 一个线程负责检查网络 , 另一 个线程管理磁盘驱动器, 还有一个线程负责显示数据, 三个线 程结合出来 共同完成文件 服务, 使用者 也可以 及时看 到网络 的变化。
在 Windows 的一个进 程内, 包 含一 个或 多个 线程。 线程 是指进程的一条执行模式, 它包 含独立的 堆栈和 CPU 寄存器 状态, 每个线程共享所 有的进 程资源, 包括 打开的 文件、信号 标识及动态分配的存储 等等。此时, 就存 在一个 线程同 步的 问题, 即怎样控制两个或多个线程同时访问同 一个数据对象。2 基于 MFC 的多线程编程在VC+ + 6. 0 附带的 MFC 类库 中, 提供了 多线程 编程的 支持, 而 且鉴于 MFC 对同步 对象作 了封装, 因此 对客户 编程 实现来说更加便捷, 避免了直接使用 Windows API 函数。在 MFC 中, 线 程分 为 两种: 用户 接口 线 程和 辅助 线 程。 用户接口线 程常用于接收 用户的输 入,处理相 应的事 件跟消 息。在 用 户 接 口 线 程 中ccriticalsection 使用, 包 含 一 个 消 息 处 理 循 环, 其 中 CWinApp 就是一个典型的事例, 它从 CWinThread 派生出 来, 负 责处理用户输入形成的事件跟消息。辅助线程常用于任务处 理( 比如计算) 不要求用户输入, 对客户而言, 它在后台运行。

我们知道: 基于 MFC 的应用 程序 有一 个要 用对 象, 它是 CWinApp 派生类 的对 象, 该 对象 代表 了 应 用进 程 的主 线 程。 当线程执 行 完( 通 常是 接 收到 WM ) QUIT 消 息) 并 退 出线 程 时, 由于进程中没有其它线程的存在, 故进程也自动结束。类 CWinApp 从 CWinThread 派生 出来, CWinThread 是 用户 接 口线 程的基类。我们在编写 线程时, 需要 从 CWinThread 派生 我们 自己的线程 类, ClassWizard 可 以给 助我 们完 成这 个工 作。创 建并开启线程后, 则线程处于运行状况; 如果线程用到共享资 源, 则必须进行资源同步处理。多线程在存取共享资源时, 都必须保护共享资源, 以免引 起冲突, 造成 错 误。MFC 为 我们 提 供 了几 个 同步 对 象C+ + 类, 即 CSyncObject、CMutex、CSemaphore、CEvent、CCriticalSection。 这里, CSyncObject 为其他四个类的基类。通常, 我们在 C + + 对象的成员变量中 使用共享 资源, 或 者把共享资源封装在 C + + 类的 内部。
我们能 将线程同 步操 作封装在对象类的实 现函数 当中, 这样在 应用中 的线程 使用 C + + 对象时, 就可以像 一般对 象一样 使用它, 简化 了使用 部 分代码的编写, 这正是面 向对象 编程的 思想。这样 编写的 类 被当做d线程安 全类d。在 设计 线程 安全 类时, 首 先要 根据 具 体状况在类中加入 一个同 步对象 类数据成 员。然后, 在类 的 成员函数中, 凡是所有更改 公共数 据以及 读取公 共数据 的地 方均要加入相应的 同步调 用。一般的 处理步 骤是: 创建一 个 CSingleLock 或者 CMultiLock 对象 , 然后 调用 其 Lock 函 数。当 对象 结束 时, 自动在 析构 函数中 调用 Unlock 函数, 当 然也 可 以在任何期望的地方调 用 Unlock 函数。如果不是在特 定的 C + + 对 象中让 用共 享资 源, 而是 在 特定的函数中 使 用共 享资 源( 这 样的 函 数称 为d线 程 安全 函 数d) , 则先确立同步 对象, 然后 调用 等待函 数, 直到 可以访 问 资源, 最后传递对同步对象的控制。下面我们探讨四个 同步对象分别适用的场合: 1) 如果某个线程 必须 等待某 些事 件出现 后才 能存取 相 应资源, 则用 CEvent; 2) 如果一个应用同时可以有多个 线程存取相 应资源, 则 用 CSemaphore; 3) 如果有多个应用( 多个进程) 同 时存取相应 资源, 则用 CMutex , 否则用 CCriticalSection。

使用线程安全类或 者线程 安全函 数进行 编程, 比不考 虑 线程安全的编程要复杂, 尤其在进行调试时状况更为复杂, 我 们需要灵活使用 VC + + 提供的调试软件, 以 保证共享资 源的 安全存取。线 程安 全编 程的 另一 缺点 是运 行效 率相 对应 低 些, 即使在单个线程运行的状况下也会代价一些强度。所以, 我们在实际工作中要 具体问 题具体分 析, 以选取 合适的 编程 方法。3 几个线程安全类的例子作者简介: 刘勇( 1972- ) , 硕士, 主要研究方向: 作战模拟.第 8期刘 勇: 如何利用 MFC 实现线程间的同步2573. 1 利用临界区对象实现泛型同 步 临界区技术是确保一次只能有一个线程访问一个数据对象的一种容易并且有效的方式。使用临界区也就声明了一个 线程共享对象。当一个线程获取了临界区对象后才能够访问 受保护的数据对象。其他线程只能期待直到此线程释放临界 区对象, 之后其它线程才 能获取 临界区 的控制 权访问 数据对 象。下面就是一个线程安全类的事例。 线程安全类 CCountArray 的头文件 CountArray. h: # include dafxmt . hd class CCountArray { privat e:int array[ 10] ; CCriticalSect ion crit icalSection; public:CCountArray( ) ; virtual ~ CCountArray( ) ; void SetArray( int value) ; void GetArray( int dstArray[ 10] ) ; }; 在头文件中包括 MFC 头文件 afxmt. h, 这样应用程序就能 使用 CCriticalSection 类。
在 CCountArray 类中 定义 了一个 数组 和一个临界区对象 criticalSection, 定义 了读函数 SetArray, 写函 数 GetArray。 线程安 全 类 CcountArray 的 实 现 函 数源 代 码 CountArray. cpp : void CCountArray: : SetArray( int value) { crit icalSection. Lock( ) ; for( int x= 0; x< 10; ++ x) array[ x] = value; crit icalSection. Unlock( ) ; }void CCountArray: : GetArray( int dstArray[ 10] ) {crit icalSection. Lock( ) ; for( int x= 0; x< 10; ++ x) dstArray[ x] = array[ x] ; crit icalSection. Unlock( ) ; } CCountArray 类的成员函数 都读取了临界区对象的锁定与 解锁 函 数。

如 果 一 个 线 程 ( A 线 程 ) 调 用 SetArray 函 数, SetArray 函数首先 调用 criticalSection. Lock( ) 函数, Lock( ) 函数 检查临界区 是否被其它线 程占有, 如果 没有则 将临界 区对象 赋给调用线程, A 线程能够访问受保护数据对象, 这样 A 线程 无须担心数据会被其它线程访问。如果另一个 线程( B 线 程) 在这时调用 SetArray 和 GetArray 函数, criticalSection. Lock( ) 会 将 B 线 程挂 起 直到 A 线 程 在执 行 完 毕, 调 用 criticalSection. Unlock( ) 释放临界区对象。然后系统 会将 B 线 程唤醒 并将临 界区对象赋给它。这样所有的线程都会有序的期待其访问受 保护数据的机会。 3. 2 利用互斥体对象实现泛型同 步 互斥体与临界区类似但最复杂。互斥体不仅可以拿来保 证同一个应 用程序中的线 程的资源 安全访 问, 还能够 用来控制不同应用程序间的轮询访问资 源。本文 不打算探讨不同应 用程序间的线程同步问 题。下面是 CCountArray2 类的 头文 件, 除 了引 入互 斥 体对 象 外, 这个头文件与 CCountArray. h 头文件基本一致。
# included afxmt . hd class CCountArray2 {pr ivate : int array[ 10] ; CMutex mutex;public: CCountArray2( ) ; virt ual ~ CCountArray2( ) ; void SetArray( int value) ; void GetArray( int dstArray[ 10] ) ;}; 线 程 安 全 类 CCountArray2 类 的 实 现 函 数 源 代 码 CountArray2. cpp: void CCountArray2: : SetArray( int value) {CSingleLock singleLock(&mutex) ; singleLock. Lock( ) ; f or( int x= 0; x< 10; + + x) array[ x] = value; }void CCountArray2: : GetArray( int dstArray[ 10] ) {CSingleLock singleLock(&mutex) ; singleLock. Lock( ) ; f or( int x= 0; x< 10; + + x) dstArray[ x] = array[ x] ; } 为了 访 问 互 斥 体 对 象, 首 先 创 建 一 个 CsingleLock 或 CMultiLock 对象。

本文只采用一个互斥体对象, 因此只声明一 个 CSingleLock 对 象。CSingleLock singleLock( &mutex) ; 构 造 函 数的参数是一个指向你必须控制 的线程同步对象的指针。只 需要调用 CSingleLock 类对象的成员变量 Lock( ) 就可以获得对 互斥体的控制权 singleLock. Lock( ) 。 如果互斥体空闲, 调用句柄就作为互斥体的占用者; 如果 另一个线程占有了互 斥体, 系统将 挂起调 用线程 直到互 斥体 被释放, 此时进入等待状态 的线程 被激活 并受到 互斥体 的控 制权。 一般, 调用 CSingleLock 对象 的成员函数 UnLock( ) 将 释放 互斥体对象。本文 的 CSingleLock 对象是 在堆中 创建的 ( 既 用 new 创 建) , 无 须 调 用 UnLock( ) 函 数。 当 函 数终 止 退 出 时, CSingleLock 对象自动读取析构函数解锁互斥体对象。 3. 3 利用信号量对象实现线程同步 信号量可以使多个 线程同 时访问 受保护 的资源, 线程 的 个数由应用程序设定。
本文来自电脑杂谈,转载请注明本文网址:
http://www.pc-fly.com/a/jisuanjixue/article-144302-1.html
我大惠州
加油