首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >问答首页 >MFC WaitForSingleObject和CCriticalSection -如何使用?

MFC WaitForSingleObject和CCriticalSection -如何使用?
EN

Stack Overflow用户
提问于 2021-04-21 10:49:46
回答 3查看 318关注 0票数 1

目前,我正在深入研究大量使用MFC类的遗留代码库。它使用CCriticalSection作为互斥对象,使用WaitForSingleObject来“锁定”该互斥对象。代码大致如下所示:

代码语言:javascript
运行
复制
struct Foo {
  static CCriticalSection mutex;

  void doSomeWriting() {
     mutex.Lock();
     …
     mutex.Unlock();
  }

  void doSomeReading() {
     WaitForSingleObject(mutex, some_timeout);
     …
     // No unlocking here!
  }
};

我会提供一个MWE,但是如果要信任我的Visual,使用MFC的最小应用程序似乎需要几千行代码。

我目前正在应用验证器下运行应用程序,该应用程序标记WaitForSingleObject()调用,并抱怨mutex中的句柄为NULL。

我知道我从代码中获得了应该驱逐CCriticalSection吗? (事实上,我们正在对其进行重构以使用std::recursive_mutex),但我至少想知道最初的作者想要用它实现什么。

不幸的是,我找不到关于WaitForSingleObjectCCriticalSection如何交互的文档。CCriticalSection文档根本没有提到WaitForSingleObjectWaitForSingleObject文档也没有提到CCriticalSection使用关键部分的示例只处理CRITICAL_SECTION对象,这些对象显然需要进行初始化。此外,在这些示例中没有使用WaitForSingleObject

我的问题

  • 是否允许在WaitForSingleObject上调用CCriticalSection
  • 在调用CCriticalSection上的WaitForSingleObject之前,是否需要以某种方式初始化它?“应用程序验证程序”错误似乎表明了这一点。
  • WaitForSingleObject锁定CCriticalSection了吗?或者它只是阻塞,直到关键部分被解锁?
  • 如果它锁定:在我的示例中,doSomeReading()方法不需要再次解锁关键部分吗?
  • 如果它不执行锁定:那么上面的示例并不保证doSomeWriting()不会在线程A中启动,而线程B正在忙着执行doSomeReading() (在WaitForSingleObject()调用之后),对吗?
EN

回答 3

Stack Overflow用户

回答已采纳

发布于 2021-04-21 17:26:32

应用验证器..。标记WaitForSingleObject()调用,并抱怨互斥体中的句柄为NULL

应用验证器是正确的。这就是正在发生的事。这也很明显,为什么会发生这种情况。这就不那么明显了,为什么微软认为强制CCriticalSection进入CSyncObject类层次结构是个好主意。

让我们从基类CSyncObject开始,它看起来像这样(与问题无关的所有内容都被去掉了):

代码语言:javascript
运行
复制
struct CSyncObject {
    CSyncObject() : m_hObject{nullptr} {}
    operator HANDLE() const { return m_hObject; }
    HANDLE m_hObject;
}

有一个默认的c‘’tor来初始化唯一的数据成员(m_hObject),还有一个转换操作符(operator HANDLE()),这样CSyncObject类型的对象就可以传递给需要HANDLE类型的参数的函数(比如WaitForSingleObject)。

除了数据成员是公共的以外,这实际上只是一个非常标准的包装器,它包含一个以HANDLE表示的本地Windows同步原语。当我们看一看CCriticalSection时,事情就开始转向一边

代码语言:javascript
运行
复制
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’to将其初始化为(应用程序验证程序告诉您的NULL )之外,它从不触及、读取或更改其他任何内容。

如果CCriticalSection没有继承公共operator HANDLE(),这就不会那么糟糕了。有了它,CCriticalSection现在可以在任何需要HANDLE的地方使用。比如,比如说,WaitForSingleObject

有了所有这些,WaitForSingleObject(mutex, some_timeout)编译,甚至运行,没有立即失败。当然,它失败了参数验证,但是当您不检查返回值时.说到底,这只是一个非常冗长的不操作。当然,它不会等待任何东西,也不会防止并发执行。它需要修理。

其余问题如下:

是否允许在WaitForSingleObject上调用CCriticalSection

当然,就像我们看到的。CCriticalSection很难伪装成一个CSyncObject,所以编译器不会介意。

在调用CCriticalSection上的WaitForSingleObject之前,是否需要以某种方式初始化它?“应用程序验证程序”错误似乎表明了这一点。

是。关键部分需要使用InitializeCriticalSectionInitializeCriticalSectionAndSpinCount初始化。这并不能使CRITICAL_SECTION成为WaitForSingleObject的合法论据。

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

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

如果它锁定:在我的示例中,doSomeReading()方法不需要再次解锁关键部分吗?

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

如果它不执行锁定:那么上面的示例并不保证doSomeWriting()不会在线程A中启动,而线程B正在忙着执行doSomeReading() (在WaitForSingleObject()调用之后),对吗?

对,是这样。线程A和B可以并发地输入doSomeWriting()。没有实现任何同步。

票数 5
EN

Stack Overflow用户

发布于 2021-04-21 20:10:14

其他成员提交的答案很好地回答了你的问题。总结如下:

  • 为一个WaitForSingleObject()对象调用CCriticalSection是错误的,因为关键部分不是一个可延迟的对象,尽管在CCriticalSection中它是从CSyncObject继承的。关键部分为相同进程的线程提供了对代码部分的独占/序列化访问(就像Mutex对象一样,但有上述限制--而且它们也更有效)。具有关键部分的唯一有效操作是“输入/离开”(锁定/解锁)。
  • 需要初始化Win32关键部分,但是CCriticalSection类构造函数(它只是它的包装器)为您完成了这一任务。
  • WaitForSingleObject()不会锁定关键部分,锁定关键部分的唯一方法是调用EnterCriticalSection() (Win32)或CCriticalSection::Lock() (MFC) --在这两种情况下都不会超时。
  • 因为这个WaitForSingleObject()调用并不真正等待或锁定任何东西(这里的使用是错误的),是的,在线程B忙于执行doSomeReading()时,可以在线程A中输入doSomeWriting()中的关键部分。但是没有两个线程可以并发地进入doSomeWriting()中的关键部分。

我认为作者试图通过以下方式实现对共享资源的同步访问:

  • 一次只有一个作家。
  • 多个并发读取器,而不进行写作。

后者应该是通过WaitForSingleObject()呼吁来实现的,但这显然是一种误解。

正如您提供的链接中所指出的,MFC的CSyncObject-derived对象有它们自己的问题,因此您需要另一种实现。

您可以使用Win32同步对象。临界截面对象实际上是可重入的(尽管我认为这一点都没有用)。它们也可以与条件变量结合使用。您可以使用这样的实现来防止作者在读取器工作时更新共享资源,以及一个(手动重置) 事件对象(由作者设置/清除),以防止读者在写入时访问资源( WaitForSingleObject()调用应该做的事情)。

然而,尽管我承认我从未真正尝试过这种机制,但我认为最适合(我敢确切地说)您想要实现的是Slim阅读器/作家(SRW)锁。它们为读写提供同步,通过使用单个对象实现“单作者-多读取器”功能。您可以将这些调用封装在一个C++类中,但是我认为这一点没有什么好处,因为Win32实现已经非常“高级”了,调用将是1:1。

票数 2
EN

Stack Overflow用户

发布于 2021-04-21 18:14:29

不是真正的答案,但作为对你的回应

使用MFC的最小应用程序似乎是几千行代码。

下面是:

代码语言:javascript
运行
复制
#include <afxmt.h>
#include <thread>
#include <chrono>

struct Foo {
    static CCriticalSection mutex;

    void doSomeWriting() {
        mutex.Lock();
        std::this_thread::sleep_for(std::chrono::seconds(5));
        mutex.Unlock();
    }

    void doSomeReading() {
        WaitForSingleObject(mutex, 1000);
        //…
        // No unlocking here!
    }
};
CCriticalSection Foo::mutex;

int main()
{
    Foo f;
    std::thread t = std::thread(&Foo::doSomeWriting, f);
    f.doSomeReading();
    t.join();
}
票数 1
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/67194360

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档