如何正确离开临界区?

8

我有下面这段C++代码,其中我使用了临界区对象(Critical Section object)

EnterCriticalSection(&cs);

// code that may throw an exception

LeaveCriticalSection(&cs);

如何确保即使抛出异常,也可以调用LeaveCriticalSection函数?
5个回答

9

只需要编写一个使用析构函数进行清理的守卫:

struct Guard {
  CriticalSection& cs;
  Guard(CriticalSection& cs)
  : cs(cs)
  {
    EnterCriticalSection(cs);
  }
  ~Guard() {
    LeaveCriticalSection(cs);
  }
  Guard(const Guard&) = delete;  
  Guard& operator = (const Guard&) = delete;
};

使用方法:

void f() {
   Guard guard(cs);
   ...
}

8

使用RAII(资源获取即初始化)习语:

struct GuardCS {
    GuardCS(CRITICAL_SECTION& p_cs) : cs(p_cs){
        EnterCriticalSection(&cs);
    }
    ~GuardCS() {
        LeaveCriticalSection(&cs);
    }
private:
    // Protect against copying, remove: =delete on pre c++11 compilers
    GuardCS(GuardCS const &) = delete;
    GuardCS& operator =(GuardCS const &) = delete;
    CRITICAL_SECTION& cs;
};

如果您恰巧使用MFC,那么有一些抽象这种东西的类:Ccriticalsection是否可用于生产环境?


如果我可以提出一个小建议,也许为了建立更简单的示例而省略它仍然是明智的,但最好将GuardCS设置为不可复制。 - user4842163

4
您可以编写一个小助手类,如下所示:

"即使抛出异常,我该怎么确保调用LeaveCriticalSection函数?"

您可以编写一个类似于以下代码的小助手类:

 class CsLocker {
 public:
     CsLocker(CriticalSection& cs)
     : cs_(cs) {
         EnterCriticalSection(&cs_);
     }
     ~CsLocker() {
          LeaveCriticalSection(&cs);
     }
     CsLocker(const CsLocker&) = delete;
     CsLocker& operator=(const CsLocker&) = delete;
 private:
     CriticalSection& cs_;
 };

这将确保每当(以及为什么)退出作用域时,临界区就会被解锁。

这很危险。如果有人意外复制了 CsLocker,你就完了。 - Lightness Races in Orbit
@LightningRacisinObrit 你说得对,我已经修复了。 - πάντα ῥεῖ

3
我建议您不要使用WinAPI关键段。您可以通过使用std::mutex来获得相同的效果。当您使用它时,您还可以使用std::lock_guard自动解锁互斥锁的RAII惯用语包装器。

更新:关键节和互斥锁之间的一个区别是您可以在一个线程上多次锁定关键节,但对于简单的std :: mutex,这并不正确。如果需要锁定的递归行为,请使用std::recursive_mutex std::lock_guard<std::recursive_mutex>

更新2:关键节和互斥锁之间的详细差异在此处描述,性能比较在此处进行。

原因:尽可能使用标准定义的机制更好。如果你使用平台特定的东西,要进行包装。所以,如果你担心性能问题,请创建一个带有锁定/解锁方法的关键部分类(以满足BasicLocakable概念要求),并使用std::lock_guard<MyCriticalSection>

为什么我不应该使用WinAPI关键段? - Nick
1
使用标准工具有很多好处。"我不需要可移植性"不是随意回避它们的好借口。 - Lightness Races in Orbit
2
我不喜欢没有理由的说“我建议你不要使用WinAPI关键段”。特别是,CriticalSection和std :: mutex或std :: recursive_mutex之间有什么区别? - user2249683
3
@Nick 关于这个问题 https://dev59.com/q2kw5IYBdhLWcg3wSorA - Dmitry Poroh
1
@DieterLücking:除非OP已经知道这种差异并且_需要_利用它,否则这也不足以避免使用标准类型。在无法使用之前请一直使用它们。 - Lightness Races in Orbit
显示剩余8条评论

1
其他答案正确,关于使用RAII对象的方法,但我认为值得指出一种使用Boost.ScopeExit实现的简单方法。
#include <boost/scope_exit.hpp>
...
EnterCriticalSection(&cs);
BOOST_SCOPE_EXIT(&cs) {
        LeaveCriticalSection(&cs);
} BOOST_SCOPE_EXIT_END
// code that may throw an exception

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接