MFCWaitForSingleObject和CCriticalSection-如何使用?
我目前正在深入研究大量使用 MFC 类的遗留代码库。它CCriticalSection用作互斥锁,并WaitForSingleObject“锁定”该互斥锁。代码大致如下:
struct Foo {
static CCriticalSection mutex;
void doSomeWriting() {
mutex.Lock();
…
mutex.Unlock();
}
void doSomeReading() {
WaitForSingleObject(mutex, some_timeout);
…
// No unlocking here!
}
};
我会提供一个 MWE,但如果要信任我的 Visual Studio,使用 MFC 的最小应用程序似乎是几千行代码。
我目前正在Application Verifier下运行该应用程序,它标记WaitForSingleObject()调用并抱怨内部的句柄mutex为 NULL。
我知道我应该从代码中删除 CCriticalSection(实际上我们正在重构它以使用它std::recursive_mutex),但我想至少知道原作者想用它实现什么。
不幸的是,我无法找到如何文档WaitForSingleObject和CCriticalSection互动。该CCriticalSection文档不提WaitForSingleObject可言,和WaitForSingleObject的文档不提CCriticalSection。使用临界区的示例仅处理CRITICAL_SECTION对象,这些对象显然需要以某种方式进行初始化。此外,WaitForSingleObject在这些示例中未使用。
我的问题
- 在呼唤
WaitForSingleObject一个CCriticalSection甚至允许? - 是否有必要
CCriticalSection在调用WaitForSingleObject它之前以某种方式初始化它?应用程序验证程序错误似乎表明了这一点。 - 是否
WaitForSingleObject锁定CCriticalSection?或者它只是阻塞直到关键部分被解锁? - 如果它锁定:
doSomeReading()我的示例中的方法不需要再次解锁临界区吗? - 如果它不做锁:那么上面的例子并不能保证
doSomeWriting()在线程 B 忙于执行doSomeReading()(WaitForSingleObject()调用后)时不在线程 A 中启动,对吗?
回答
应用程序验证程序 [...] 标记
WaitForSingleObject()调用并抱怨互斥锁中的句柄为NULL.
应用程序验证器是正确的。这正是正在发生的事情。这也很明显,为什么会发生这种情况。不太明显,为什么 Microsoft 决定将CCriticalSection强制加入CSyncObject类层次结构是一个好主意。
让我们从基类开始,CSyncObject它看起来像这样(与问题无关的所有内容都被剥离了):
struct CSyncObject {
CSyncObject() : m_hObject{nullptr} {}
operator HANDLE() const { return m_hObject; }
HANDLE m_hObject;
}
有一个默认的 c'tor 初始化唯一的数据成员 ( m_hObject) 和一个转换运算符 ( operator HANDLE()) 以便CSyncObject可以将类型的对象传递给需要类型参数HANDLE(如WaitForSingleObject)的函数。
除了公开的数据成员之外,这实际上只是一个非常标准的包装器,它围绕原生 Windows 同步原语表示为HANDLE. 当我们看一下时,事情开始横盘整理CCriticalSection:
struct CCriticalSection : CSyncObject { // <- Look ma, I'm a CSyncObject!
CCriticalSection() { /* Initialize m_sect */ }
operator CRITICAL_SECTION*() { return &m_sect; }
CRITICAL_SECTION m_sect;
}
同样,一个公共数据成员,无论如何都要欣赏一致性。但是看,还有更多!CCriticalSection继承自CSyncObject,所以现在它也有一个m_hObject。正如您可能已经猜到的那样,它从不接触、读取或更改除基类的 c'tor 将其初始化为以外的任何内容(NULL应用程序验证程序告诉您的)。
如果没有也继承 public ,这也不会那么糟糕。有了它,现在可以在预期的任何地方使用 a 。比如说,。CCriticalSectionoperator HANDLE()CCriticalSectionHANDLEWaitForSingleObject
有了所有这些,WaitForSingleObject(mutex, some_timeout)编译甚至运行都不会立即失败。当然,它无法通过参数验证,但是当您不检查返回值时......它归结为只是一个非常冗长的无操作。它当然不会等待任何东西,也不会防止并发执行。它需要修复。
剩下的问题:
在呼唤
WaitForSingleObject一个CCriticalSection甚至允许?
当然,正如我们所见。CCriticalSection假装足够努力,CSyncObject以便编译器不会介意。
是否有必要
CCriticalSection在调用WaitForSingleObject它之前以某种方式初始化它?应用程序验证程序错误似乎表明了这一点。
是的。临界区需要使用InitializeCriticalSection或InitializeCriticalSectionAndSpinCount 进行初始化。这CRITICAL_SECTION对WaitForSingleObject.
是否
WaitForSingleObject锁定CCriticalSection?或者它只是阻塞直到关键部分被解锁?
不,但EnterCriticalSection将获得所有权,这是通过调用LeaveCriticalSection 发布的。
如果它锁定:
doSomeReading()我的示例中的方法不需要再次解锁临界区吗?
如果它使用正确的 API 调用来取得所有权,它就会这样做。因为它什么都不做,所以也没有必要释放任何东西。
如果它不做锁:那么上面的例子并不能保证
doSomeWriting()在线程 B 忙于执行doSomeReading()(WaitForSingleObject()调用后)时不在线程 A 中启动,对吗?
正确的。线程A和B可以doSomeWriting()同时进入。没有实现任何同步。