【问题标题】:Wait for a detached thread to finish in C++等待分离线程在 C++ 中完成
【发布时间】:2010-12-16 17:21:03
【问题描述】:

如何在 C++ 中等待分离的线程完成?

我不关心退出状态,我只想知道线程是否完成。

我正在尝试为异步第三方工具提供同步包装器。问题是涉及回调的奇怪竞争条件崩溃。进度是:

  1. 我打电话给第三方,注册回调
  2. 当第三方完成时,它会使用回调通知我 -- 在一个我无法真正控制的分离线程中。
  3. 我希望 (1) 中的线程等到 (2) 被调用。

我想将它封装在一个提供阻塞调用的机制中。到目前为止,我有:

class Wait {
  public:
  void callback() {
    pthread_mutex_lock(&m_mutex);
    m_done = true;
    pthread_cond_broadcast(&m_cond);
    pthread_mutex_unlock(&m_mutex);
  }

  void wait() {
    pthread_mutex_lock(&m_mutex);
    while (!m_done) {
      pthread_cond_wait(&m_cond, &m_mutex);
    }
    pthread_mutex_unlock(&m_mutex);
  }

  private:
  pthread_mutex_t m_mutex;
  pthread_cond_t  m_cond;
  bool            m_done;
};

// elsewhere...
Wait waiter;
thirdparty_utility(&waiter);
waiter.wait();

据我所知,这应该可以工作,而且通常可以,但有时会崩溃。据我从corefile中可以确定,我对问题的猜测是这样的:

  1. 当回调广播 m_done 结束时,等待线程被唤醒
  2. 等待线程现在在这里完成,等待被销毁。 Wait 的所有成员都被销毁,包括 mutex 和 cond。
  3. 回调线程尝试从广播点继续,但现在正在使用已释放的内存,这会导致内存损坏。
  4. 当回调线程尝试返回时(高于我糟糕的回调方法的级别),程序崩溃(通常使用 SIGSEGV,但我见过几次 SIGILL)。

我尝试了很多不同的机制来尝试解决这个问题,但没有一个能解决问题。我仍然看到偶尔的崩溃。

编辑:更多细节:

这是大规模多线程应用程序的一部分,因此创建静态等待是不切实际的。

我运行了一个测试,在堆上创建 Wait,并故意泄漏内存(即 Wait 对象永远不会被释放),结果没有崩溃。所以我确定这是 Wait 过早释放的问题。

wait 解锁后,我还尝试使用sleep(5) 进行测试,这也没有产生崩溃。不过,我讨厌依赖这样的组合。

编辑:第三方详细信息:

一开始我不认为这是相关的,但我越想越觉得这是真正的问题:

我提到的第三方的东西,以及为什么我无法控制线程:这是使用 CORBA。

因此,CORBA 持有对我的对象的引用的时间可能比预期的要长。

【问题讨论】:

    标签: c++ multithreading pthreads corba


    【解决方案1】:

    是的,我相信您所描述的正在发生(释放时的竞争条件)。解决此问题的一种快速方法是创建 Wait 的静态实例,该实例不会被破坏。只要您不需要同时有多个服务员,这将起作用。

    您还将永久使用该内存,它不会被释放。不过看起来也不算太糟糕。

    主要问题是很难在线程之间协调线程通信构造的生命周期:在可以安全销毁时,您将始终需要至少一个剩余的通信构造进行通信(至少在没有垃圾收集的语言中,例如 C++ )。

    编辑: 有关使用全局互斥体进行引用计数的一些想法,请参阅 cmets。

    【讨论】:

    • 不幸的是,这是在一个大规模多线程应用程序中,我们真的希望为每个单独的等待对象 - 否则它会拖慢我们太多。
    • 另外,如果我们使用静态Wait,就会出现试图协调哪个线程需要恢复的问题。
    • 好的,你可以这样做。您可以将 refcount 字段添加到受全局互斥体保护的 Wait 对象。从 2 开始 refcount,然后让回调和服务员在完成后减少 refcount。如果全局互斥锁成为您的瓶颈,还有其他更复杂的解决方案。
    • 另外,如果您可以对引用计数使用原子操作,则不需要全局互斥锁。
    • 太棒了!引用计数实际上是由 CORBA 提供的,但这解决了问题!谢谢!
    【解决方案2】:

    据我所知,没有可移植的方式直接询问线程是否已完成运行(即没有 pthread_ 函数)。你正在做的正确的方法,至少就你发出信号的条件而言。如果您看到崩溃并确定是由于在创建它的线程退出时释放了 Wait 对象(而不是一些其他微妙的锁定问题——太常见了),那么问题是您需要确保Wait 没有 被释放,方法是从执行通知的线程以外的线程进行管理。将其放入全局内存或动态分配并与该线程共享。最简单的情况是,不让正在等待的线程拥有等待的内存,而是让正在等待的线程拥有它。

    【讨论】:

      【解决方案3】:

      您是否正确初始化和销毁​​互斥锁​​和条件变量?

      Wait::Wait()
      {
          pthread_mutex_init(&m_mutex, NULL);
          pthread_cond_init(&m_cond, NULL);
          m_done = false;
      }
      
      Wait::~Wait()
      {
          assert(m_done);
          pthread_mutex_destroy(&m_mutex);
          pthread_cond_destroy(&m_cond);
      }
      

      确保您没有过早地破坏 Wait 对象——如果它在一个线程中被破坏而另一个线程仍然需要它,您将获得可能导致段错误的竞争条件。我建议将其设为全局静态变量,在程序初始化时(main() 之前)构建并在程序退出时销毁。

      【讨论】:

      • 是的,mutex 和 cond 已正确初始化/销毁。我实际上在那些已经过良好测试的产品上使用了包装类。是的,我确信 Wait 被过早地破坏了——而一个线程仍在 Wait::callback 中。
      【解决方案4】:

      如果您的假设是正确的,那么第三方模块似乎有问题,您需要想出一些技巧来使您的应用程序正常工作。

      静态Wait 不可行。 Wait pool 怎么样(它甚至可以按需增长)?您的应用程序是否使用线程池运行? 尽管仍然有可能在第三方模块仍在使用它时重复使用相同的Wait。但是,您可以通过正确排队池中的空位等待来最大程度地减少这种机会。

      免责声明:我绝不是线程安全方面的专家,因此请将此帖子视为外行的建议。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2010-09-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多