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),但我想至少知道原作者想用它实现什么。

不幸的是,我无法找到如何文档WaitForSingleObjectCCriticalSection互动。该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_SECTIONWaitForSingleObject.

是否WaitForSingleObject锁定CCriticalSection?或者它只是阻塞直到关键部分被解锁?

不,但EnterCriticalSection将获得所有权,这是通过调用LeaveCriticalSection 发布的。

如果它锁定:doSomeReading()我的示例中的方法不需要再次解锁临界区吗?

如果它使用正确的 API 调用来取得所有权,它就会这样做。因为它什么都不做,所以也没有必要释放任何东西。

如果它不做锁:那么上面的例子并不能保证doSomeWriting()在线程 B 忙于执行doSomeReading()WaitForSingleObject()调用后)时不在线程 A 中启动,对吗?

正确的。线程A和B可以doSomeWriting()同时进入。没有实现任何同步。


以上是MFCWaitForSingleObject和CCriticalSection-如何使用?的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>