在C++中等待一个分离线程结束

9

如何在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();

据我所知,这应该可以工作,并且通常也能工作,但有时会崩溃。从核心文件中可以确定问题是这样的:
  1. 当回调广播 m_done 的结束时,等待线程会被唤醒
  2. 等待线程现在已经完成,Wait 被销毁。Wait 的所有成员都被销毁,包括互斥锁和条件变量。
  3. 回调线程尝试从广播点继续执行,但现在正在使用已释放的内存,导致内存损坏。
  4. 当回调线程尝试返回(在我的糟糕回调方法的上面)时,程序会崩溃(通常是 SIGSEGV,但我几次看到过 SIGILL)。
我尝试了很多不同的机制来解决这个问题,但都没有解决问题。我仍然会偶尔遇到崩溃。
编辑:更多细节:
这是一个大规模多线程应用程序的一部分,因此创建静态 Wait 并不实际。
我运行了一个测试,在堆上创建 Wait,并故意泄漏内存(即 Wait 对象从未被释放),结果没有崩溃。所以我确定这是 Wait 被释放得太早的问题。
我还尝试了在 wait 中解锁后使用 sleep(5) 的测试,也没有出现崩溃。虽然我不喜欢依赖这样的 hack。
编辑:第三方细节:
一开始我认为这与问题无关,但越想越觉得这是真正的问题:
我提到的第三方东西,以及为什么我无法控制线程:这是使用 CORBA。
因此,CORBA 可能会比预期更长时间地保留对我的对象的引用。
4个回答

3

是的,我相信你描述的情况正在发生(在解除分配时存在竞态条件)。一种快速修复方法是创建一个不会被销毁的Wait的静态实例。只要您不需要同时拥有多个等待者,这将起作用。

您还将永久使用该存储器,它不会解除分配。但看起来并不太糟糕。

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

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


不幸的是,这是在一个大规模多线程应用程序中,我们真的希望为每个线程使用单独的等待对象 - 否则会严重拖慢我们的速度。 - Tim
另外,如果我们使用静态等待,那么就存在协调哪个线程需要恢复的问题。 - Tim
好的,你可以这样做。你可以为Wait对象添加一个refcount字段,并受全局互斥保护。将refcount从2开始,当回调和等待者完成后,都减少refcount。如果全局互斥成为瓶颈,还有其他更复杂的解决方案。 - Adam Goode
1
此外,如果您可以使用原子操作来处理引用计数,则不需要全局互斥锁。 - Adam Goode
太好了!引用计数实际上是由CORBA提供的,但这解决了问题!谢谢! - Tim

0

你是否正确地初始化和销毁了互斥锁和条件变量?

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中时。 - Tim

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

0

如果您的假设是正确的,那么第三方模块似乎存在缺陷,您需要想出某种方法来使您的应用程序正常工作。

静态Wait不可行。如何使用Wait池(它甚至可以根据需求增长)?您的应用程序是否使用线程池运行? 虽然在第三方模块仍在使用同一Wait时仍有可能被重复使用。但是,通过在池中适当排队空闲等待,您可以将这种机会最小化。

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


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