多线程编程:何时使用同步类
MFC 提供的六种多线程类分为两类:同步对象(CSyncObject、CSemaphore、CMutex、CCriticalSection 和 CEvent)和同步访问对象(CMultiLock 和 CSingleLock)。
当必须控制对资源的访问以确保资源的完整性时,使用同步类。同步访问类用于获取对这些资源的访问权。此文章描述各个类的使用时间。
若要确定应使用的同步类,请询问以下一系列问题:
应用程序必须等到发生某事才能访问资源(例如,在将数据写入文件之前,必须先从通信端口接收它)吗? 如果是,则使用 CEvent。
同一应用程序内一个以上的线程可以同时访问此资源(例如,应用程序允许在同一文档上最多同时打开五个带有视图的窗口)吗? 如果是,请使用 CSemaphore。
可以有一个以上的应用程序使用此资源(例如,资源在 DLL 中)吗? 如果是,请使用 CMutex。
如果不是,请使用 CCriticalSection。
从不直接使用 CSyncObject。它是其他四个同步类的基类。
示例 1:使用三个同步类以维护链接的帐户列表的应用程序为例。此应用程序允许在独立的窗口中最多检查三个帐户,但是在任何特定的时间,只能更新一个帐户。更新帐户后,通过网络将更新的数据发送到数据存档。
此示例应用程序使用所有这三种类型的同步类。因为它一次最多允许检查三个帐户,因此使用 CSemaphore 限制对三个视图对象的访问。当试图查看第四个帐户时,应用程序或者等到前三个窗口中有一个关闭,或者该尝试失败。更新帐户时,应用程序使用 CCriticalSection 确保一次只更新一个帐户。更新成功后,发出信号 CEvent 以释放等待该事件信号发送的线程。此线程将新数据发送到数据存档。
示例 2:使用同步访问类选择要使用的同步访问类更为简单。如果应用程序只与访问单个受控资源有关,请使用 CSingleLock。如果需要访问多个受控资源中的任何一个,则使用 CMultiLock。在示例 1 中,应该使用 CSingleLock,因为在每种情况下,任何特定时间都只需要一个资源。
//=========================================================================================
Visual C++ 概念:添加功能
多线程编程:如何使用同步类
写入多线程应用程序时,线程间的同步资源访问是一个常见问题。两个或多个线程同时访问同一数据会导致不合需要的、不可预知的结果。例如,一个线程可能正在更新结构的内容,而另一个线程正在读取同一结构的内容。无法得知读取线程将会收到何种数据:旧数据、新写入的数据或两种数据都有。MFC 提供了多个同步和同步访问类以帮助解决此问题。此文章解释了可用的类以及如何在典型的多线程应用程序中使用它们创建线程安全类。
典型的多线程应用程序具有代表各个线程间要共享的资源的类。正确设计的完全线程安全类不需要调用任何同步函数。该类的任何事情都在内部处理,使您可以将精力集中于如何更好地使用类,而不是它如何会损坏。创建完全线程安全类的最佳技术是将同步类合并到资源类中。将同步类合并到共享类是一个简单的过程。
以维护链接的帐户列表的应用程序为例。此应用程序允许在独立的窗口中最多检查三个帐户,但是在任何特定的时间,只能更新一个帐户。更新帐户后,通过网络将更新的数据发送到数据存档。
此示例应用程序使用所有这三种类型的同步类。因为它允许一次最多检查三个帐户,因此使用 CSemaphore 限制对三个视图对象的访问。当试图查看第四个帐户时,应用程序或者等到前三个窗口中有一个关闭,或者该尝试失败。更新帐户时,应用程序使用 CCriticalSection 确保一次只更新一个帐户。更新成功后,发出信号 CEvent 以释放等待该事件信号发送的线程。此线程将新数据发送到数据存档。
设计线程安全类若要使类完全线程安全,首先将适当的同步类作为数据成员添加到共享类中。在前面的帐户管理示例中,将 CSemaphore 数据成员添加到视图类,将 CCriticalSection 数据成员添加到链接的列表类,将 CEvent 数据成员添加到数据存储类。
下一步,将同步调用添加到修改类中的数据或访问受控资源的所有成员函数中。应该在每个函数中创建 CSingleLock 或 CMultiLock 对象,并调用对象的 Lock 函数。当锁定对象超出范围并被销毁时,该对象的析构函数调用 Unlock 以释放资源。当然,如果愿意,可直接调用 Unlock。
用这种方式设计线程安全类使得在多线程应用程序中使用该类与使用非线程安全类一样容易,但却具有完全安全性。将同步对象和同步访问权对象封装到资源的类将提供完全线程安全编程的所有优点,而不会有维护同步代码的缺点。
以下代码示例通过使用在共享资源类和 CSingleLock 对象中声明的数据成员 m_CritSection(CCriticalSection 类型),对此方法进行了说明。通过使用 m_CritSection 对象的地址创建 CSingleLock 对象,来试图同步共享资源(从 CWinThread 派生)。试图锁定资源,一旦锁定,即完成了共享对象上的工作。一旦完成工作,即调用 Unlock 取消锁定资源。
CSingleLock singleLock(&m_CritSection);
singleLock.Lock();
// resource locked
//.usage of shared resource...
singleLock.Unlock();
注意 与其他 MFC 同步类不同,CCriticalSection 没有计时锁定请求选项。等待释放线程的时间是无限的。此方法的缺点是类将要比没有添加同步对象的相同类慢一些。而且,如果有一个以上的线程可能删除对象,合并方法不一定总是有效。在这种情况下,最好维持单独同步对象。