多线程处理:如何使用 MFC 同步类

编写多线程应用程序时,一个常见的问题是在线程之间同步资源访问。 让两个或更多个线程同时访问相同的数据可能会导致不可取和不可预测的结果。 例如,一个线程可能正在更新某个结构的内容,而另一个线程正在读取该结构的内容。 读取线程将接收的数据是未知的:旧数据、新写入的数据,或者可能是这两者的混合。 MFC 提供了许多同步和同步访问类来帮助解决此问题。 本主题介绍可用的类,并说明如何使用这些类在典型的多线程应用程序中创建线程安全类。

典型的多线程应用程序具有一个表示要在线程之间共享的资源的类。 设计良好的完全线程安全类不需要你调用任何同步函数。 所有内容都在内部处理到类,使你能够关注如何以最佳方式使用该类,而不是它可能会如何遭到损坏。 要创建完全线程安全的类,一个有效的方法是将同步类合并到资源类中。 将同步类合并到共享类的过程很简单。

以维护帐户链接列表的应用程序为例。 此应用程序允许在单独的窗口中检查最多 3 个帐户,但在任何特定时间都只能更新一个帐户。 更新帐户时,更新后的数据会通过网络发送到数据存档。

此示例应用程序使用所有三种类型的同步类。 它允许一次检查最多 3 个帐户,因此它使用 CSemaphore 来限制对 3 个视图对象的访问。 尝试查看第 4 个帐户时,应用程序会等到前 3 个窗口中某个窗口关闭,否则尝试失败。 更新帐户时,应用程序使用 CCriticalSection 来确保一次只更新一个帐户。 更新成功后,它会向 CEvent 发出信号,后者会释放一个等待事件接收信号的线程。 此线程将新数据发送到数据存档。

设计线程安全类

要使类完全线程安全,请先将适当的同步类作为数据成员添加到共享类。 在先前的帐户管理示例中,CSemaphore 数据成员添加到视图类中,CCriticalSection 数据成员添加到链接列表类中,CEvent 数据成员添加到数据存储类中。

接下来,向修改类中的数据或访问受控资源的所有成员函数添加同步调用。 在每个函数中,应创建 CSingleLockCMultiLock 对象,并调用该对象的 Lock 函数。 当锁对象超出范围并被销毁时,对象的析构函数会调用 Unlock,从而释放资源。 当然,如果需要,你可直接调用 Unlock

以这种方式设计线程安全类后,此类可以像非线程安全类一样轻松地在多线程应用程序中使用,但具有更高的安全级别。 将同步对象和同步访问对象封装到资源类中可提供完全线程安全编程的所有优势,但无需维护同步代码。

下面的代码示例使用数据成员 m_CritSection(类型为 CCriticalSection,在共享资源类中声明)和对象 CSingleLock 来演示此方法。 通过使用 m_CritSection 对象的地址创建 CSingleLock 对象,来尝试同步共享资源(派生自 CWinThread)。 尝试锁定资源,并在获得资源后对共享对象执行操作。 完成操作后,调用 Unlock 来解锁资源并。

CSingleLock singleLock(&m_CritSection);
singleLock.Lock();
// resource locked
//.usage of shared resource...

singleLock.Unlock();

注意

与其他 MFC 同步类不同,CCriticalSection 没有计时锁请求选项。 永远等不到线程变为空闲线程。

此方法的缺点是,该类的速度比没有添加同步对象的类稍微慢一些。 此外,如果存在有多个线程可能会删除该对象的情况,则合并方法可能并不总是有效。 如果无效,最好维护单独的同步对象。

若要了解如何确定在不同情况下要使用的同步类,请参阅多线程处理:何时使用同步类。 有关同步的详细信息,请参阅 Windows SDK 中的同步。 若要详细了解 MFC 中的多线程处理支持,请参阅使用 C++ 和 MFC 进行多线程处理

另请参阅

使用 C++ 和 MFC 进行多线程编程