【问题标题】:How to properly leave a Critical Section?如何正确离开关键部分?
【发布时间】:2015-07-11 04:06:47
【问题描述】:

我有以下使用Critical Section object 的C++ 代码:

EnterCriticalSection(&cs);

// code that may throw an exception

LeaveCriticalSection(&cs);

即使抛出异常,如何确保LeaveCriticalSection函数被调用?

【问题讨论】:

    标签: c++ windows exception exception-handling synchronization


    【解决方案1】:

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

    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);
       ...
    }
    

    【讨论】:

      【解决方案2】:

      使用 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,那么有一些类可以抽象出这些东西:is Ccriticalsection usable in production?

      【讨论】:

      • 如果我可以提出一个小建议,也许为了一个更简单的示例而忽略它仍然是明智的,让 GuardCS 不可复制可能是健康的。
      【解决方案3】:

      “如何确保在抛出异常的情况下调用 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,你就完蛋了。
      • @LightningRacisinObrit 你说得对,我已经解决了。
      【解决方案4】:

      我建议你不要使用 WinAPI 临界区。您可以使用std::mutex 获得相同的结果。当您使用它时,您还可以使用RAII idiom 包装器来自动解锁互斥锁(std::lock_guard)。

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

      更新 2:在here 描述了关键部分和互斥锁之间的详细区别,性能比较是here

      原因:最好尽可能使用标准定义的机制。如果您使用特定于平台的东西 - 将其包裹起来。因此,如果您担心性能 - 使用锁定/解锁方法创建关键部分类(以满足 BasicLocakable 概念要求)并使用 std::lock_guard<MyCriticalSection>

      【讨论】:

      • 为什么我不应该使用 WinAPI 临界区?
      • 使用标准工具有很多好处。 “我不需要可移植性”并不是任意回避它们的充分借口。
      • 我不喜欢“我建议您不要使用 WinAPI 临界区”的说法。没有推理。特别是,CriticalSection 和 std::mutex 或 std::std::recursive_mutex 之间有什么区别?
      • @DieterLücking:除非 OP 已经意识到这种差异并且 需要 使用它,否则这也是避免标准类型的充分理由。 使用它们直到你不能。
      【解决方案5】:

      关于使用 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
      

      【讨论】:

        猜你喜欢
        • 2023-01-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-09-19
        • 1970-01-01
        • 2023-03-23
        • 2016-04-26
        • 2015-03-31
        相关资源
        最近更新 更多