【问题标题】:C++ Exception Handling In Critical Section (pthreads)临界区中的 C++ 异常处理 (pthreads)
【发布时间】:2010-01-06 21:42:59
【问题描述】:

[编辑:(从评论中复制)事实证明,问题出在其他地方,但感谢大家的意见。]

我有一个共享容器类,它使用单个互斥锁来锁定 push() 和 pop() 函数,因为我不想同时修改头部和尾部。代码如下:

int Queue::push( WorkUnit unit )
{
    pthread_mutex_lock( &_writeMutex );
    int errorCode = 0;

    try
    {
        _queue.push_back( unit );
    }
    catch( std::bad_alloc )
    {
        errorCode = 1;
    }

    pthread_mutex_unlock( &_writeMutex );

    return errorCode;
}

当我在调试模式下运行它时,一切都很好。当我在发布模式下运行时,大约在驱动程序开始“同时”推送和弹出时发生崩溃。如果 try/catch 块捕获到 std::bad_alloc 异常,它会立即强制退出吗?如果是这样,我应该将函数的其余部分包含在 finally 块中吗?

另外,较慢的调试模式是否可能仅因为我的 push() 和 pop() 调用实际上从未同时发生而成功?

【问题讨论】:

  • 崩溃的性质是什么?你也可以发布pop() 函数吗?

标签: c++ exception mutex pthreads


【解决方案1】:

在 C++ 中,我们使用Resource Acquisition Is Initialization (RAII) 来防范异常。

【讨论】:

    【解决方案2】:

    这真的是异常后的轰炸吗?您的 sn-p 更有可能是您的同步不正确。以您的互斥锁的名称开头:“writeMutex”。如果还有“readMutex”,这将不起作用。所有读取、窥视和写入操作都需要由相同的互斥锁锁定。

    【讨论】:

    • 正如我所说,我使用“单个互斥锁来锁定 push() 和 pop() 函数”,它恰好被称为“_writeMutex”。事实证明,问题出在其他地方,但感谢大家的意见。
    • 那么,你要改名吗?
    【解决方案3】:

    try/catch 是否立即阻塞 如果它捕获到一个,则强制退出 std::bad_alloc 异常?

    没有。如果在 try {...} 块内抛出 std::bad_alloc,则 catch {...} 块中的代码将触发。

    如果您的程序实际上正在崩溃,那么您的 push_back 调用似乎抛出了除 bad_alloc 之外的一些异常(未在您的代码中处理),或者 bad_alloc 被抛出 @ 之外的某个地方987654325@块。

    顺便问一下,你确定你真的要在这里使用 try...catch 块吗?

    【讨论】:

    • 不幸的是,我需要使用 try/catch 块(无论如何最终都会捕获 std::exception),因为调用函数可以选择在push() 函数中任何类型的失败事件。这是一个巨大的 CF 的所有部分,我们需要在没有编写大部分内容的开发人员的帮助下开始工作:(
    【解决方案4】:

    流行是什么样子的

    创建一个锁包装类,当它超出范围时会自动释放锁(如 RAII 注释)

    c++ 没有 finally(感谢 stoustrop 先生的粗心)

    我会抓住 std::exception 或根本没有(鸭子低头进行火焰战)。如果你没有抓住,那么你需要包装器

    【讨论】:

    • 幸运的是我们还没有定稿。它使代码更难正确编写。析构函数是一个更好的特性,并提供了更简洁的实现。详情请见stackoverflow.com/questions/161177/…
    【解决方案5】:

    关于发布/调试:是的,您经常会发现两种构建类型之间的竞争条件发生变化。当您处理同步时,您的线程将以不同级别的训练运行。写得好的线程将主要并发运行,而写得不好的线程将相对于彼此以高度同步的方式运行。所有类型的同步都会产生某种级别的同步行为。好像同步和同步来自同一个词根……

    所以是的,鉴于调试和发布之间的运行时性能略有不同,线程同步的那些点有时会导致错误代码出现在一种类型的构建中,而不是另一种。

    【讨论】:

      【解决方案6】:

      您需要使用 RAII
      这基本上意味着使用构造函数/析构函数来锁定/解锁资源。
      这保证了互斥锁将始终处于解锁状态,即使出现异常也是如此。

      您应该只使用一个互斥锁来访问列表。
      即使您有一个只读互斥锁,该互斥锁由一个只读取的线程使用。这并不意味着当另一个线程正在更新队列时读取是安全的。队列可能处于由线程调用 push() 引起的某种中间状态,而另一个线程正在尝试导航 invlide 中间状态。

      class Locker
      {
          public:
              Locker(pthread_mutex_t &lock)
                  :m_mutex(lock)
              {
                  pthread_mutex_lock(&m_mutex);
              }
              ~Locker()
              {
                  pthread_mutex_unlock(&m_mutex);
              }
          private:
              pthread_mutex_t&    m_mutex;
      };
      
      int Queue::push( WorkUnit unit )
      {
          // building the object lock calls the constructor thus locking the mutex.
          Locker  lock(_writeMutex);
          int errorCode = 0;
      
          try
          {
              _queue.push_back( unit );
          }
          catch( std::bad_alloc )  // Other exceptions may happen here.
          {                        // You catch one that you handle locally via error codes. 
              errorCode = 1;       // That is fine. But there are other exceptions to think about.
          }
      
          return errorCode;
      }  // lock destructor called here. Thus unlocking the mutex.
      

      PS。我讨厌使用前导下划线。
      虽然从技术上讲,这里没问题(假设成员变量),但很容易搞砸,我不想在标识符前面加上''。有关标识符名称中的“”的完整规则列表,请参阅What are the rules about using an underscore in a C++ identifier?

      【讨论】:

      • 我目前遇到了类似的问题,虽然我确实创建了自己的 Locker ,但我不太确定如何处理错误。我是 ctor,我可以在锁定失败时抛出异常,但我也想知道解锁失败的时间,但我对从 dtor 抛出异常感觉不太好。并且创建close 方法有点违背了在作用域末端关闭互斥锁的包装器的目的。您对此有何看法?
      【解决方案7】:

      以前的 Locker 类代码示例有一个主要问题: 当 pthread_mutex_lock() 失败时,你会怎么做? 答案是此时你必须从构造函数中抛出异常,并且它可以被捕获。 美好的。 然而, 根据 c++ 异常规范,从析构函数中抛出异常是禁止的。 您如何处理 pthread_mutex_unlock 失败?

      【讨论】:

        【解决方案8】:

        在任何检测软件下运行代码都没有任何用处。 你必须正确的代码,而不是在 valgrind 下运行。

        在 C 中它工作得非常好:

        pthread_cleanup_pop( 0 );
        r = pthread_mutex_unlock( &mutex );
        if ( r != 0 )
        {
            /* Explicit error handling at point of occurance goes here. */
        }
        

        但是因为 c++ 是一种软件流产,所以没有任何合理的方法可以确定地处理线程编码故障。像将 pthread_mutex_t 包装到一个添加某种状态变量的类中这样的脑死想法就是 - 脑死。以下代码不起作用:

        Locker::~Locker()
        {
            if ( pthread_mutex_unlock( &mutex ) != 0 )
            {
                failed = true; // Nonsense.
            }
        }
        

        而这样做的原因是,在 pthread_mutex_unlock() 很好地返回该线程之后,可能会从 cpu 中切出 - 被抢占。这意味着 .failed 公共变量仍然是错误的。其他查看它的线程会得到错误的信息 - 状态变量表示没有失败,同时 pthread_mutex_unlock() 失败。即使幸运的是,这两个语句同时运行,该线程也可能在 ~Locker() 返回之前被抢占,其他线程可能会修改 .failed 的值。归根结底,这些方案不起作用 - 应用程序定义的变量没有原子测试和设置机制。

        有人说,析构函数永远不应该有失败的代码。其他任何东西都是糟糕的设计。好的。我只是想知道在 c++ 中什么是 100% 异常和线程安全的好设计。

        【讨论】:

          猜你喜欢
          • 2010-12-11
          • 1970-01-01
          • 2010-10-14
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多