【问题标题】:how to let a thread wait for destruction of an object如何让线程等待对象的销毁
【发布时间】:2012-06-29 21:18:43
【问题描述】:

我想让一个线程等待另一个线程销毁特定对象。我想过以某种方式实现它:

class Foo {
private:
    pthread_mutex_t* mutex;
    pthread_cond_t* condition;
public:
    Foo(pthread_mutex_t* _mutex, pthread_cond_t* _condition) : mutex(_mutex), condition(_condition) {}

    void waitForDestruction(void) {
        pthread_mutex_lock(mutex);
        pthread_cond_wait(condition,mutex);
        pthread_mutex_unlock(mutex);
    }

    ~Foo(void) {
        pthread_mutex_lock(mutex);
        pthread_cond_signal(condition);
        pthread_mutex_unlock(mutex);
    }
};

然而,我知道我必须在 waitForDestruction 方法中处理虚假唤醒,但我不能在 'this' 上调用任何东西,因为它可能已经被破坏了。

我想到的另一种可能性是不使用条件变量,而是在构造函数中锁定互斥锁,在析构函数中解锁它并在 waitForDestruction 方法中锁定/解锁它 - 这应该适用于非递归互斥锁,而iirc我可以从一个没有锁定它的线程中解锁一个互斥锁,对吧?第二种选择是否会受到任何虚假唤醒的影响?

【问题讨论】:

  • 抛开设计不谈,如果你让构造函数创建互斥锁和条件,而不是作为参数传递,它们对于每个实例都是唯一的,你不必担心杂散信号。但是,您需要有适当的复制构造函数和赋值运算符来创建新的互斥体/条件。
  • 忘记缺少的while 循环,您根本无法像那样实现waitForDestruction。您要求对象检查它是否存在。您需要将同步拆分为另一个对象。
  • 我认为您应该考虑创建一个单独的对象,第一个线程等待该对象并触发相关对象的析构函数。
  • @all - 是的,我知道这是糟糕的设计,这个问题更多的是出于兴趣而不是出于实际实施它的需要。 @Joachim,我认为这会引入更多问题,例如在waitForDestruction 中等待的线程访问互斥锁时必须销毁析构函数中的互斥锁。 @Potatoswatter:嗯,你是对的,如果在对象被破坏后调用waitForDestruction,就会出现竞争条件。这很糟糕,可能无法修复。我想你不应该做那样的事情是有原因的:)
  • 嗯,另一个想法:是否可以使用容量为 1 的信号量(在 Foo 之外创建),在 ctor 中抓取,在 dtor 中释放。然后调用线程可以尝试获取信号量并将阻塞直到Foo 释放它。 @Tom:基本上是您的想法,我认为为此目的使用信号量就可以了

标签: c++ synchronization pthreads conditional-statements


【解决方案1】:

这总是一件困难的事。但是这些代码行怎么样:

struct FooSync {

    typedef boost::shared_ptr<FooSync> Ptr;

    FooSync() : owner(boost::this_thread::get_id()) {
    }

    void Wait() {
        assert(boost::this_thread::get_id() != owner);
        mutex.lock();
        mutex.unlock();
    }

    boost::mutex mutex;
    boost::thread::id owner;

};

struct Foo {

    Foo() { }

    ~Foo() {
        for (size_t i = 0; i < waiters.size(); ++i) {
            waiters[i]->mutex.unlock();
        }
    }

    FooSync::Ptr GetSync() {
        waiters.push_back(FooSync::Ptr(new FooSync));
        waiters.back()->mutex.lock();
        return waiters.back();
    }

    std::vector<FooSync::Ptr> waiters;

};

上述解决方案将允许在单个Foo 对象上存在任意数量的销毁等待对象。只要它会正确管理这些对象占用的内存。似乎没有什么可以阻止在堆栈上创建 Foo 实例。

虽然我看到的唯一缺点是它要求销毁等待对象总是在“拥有”Foo 对象实例的线程中创建,否则可能会发生递归锁。还有更多,如果GetSync 被多线程调用,可能会在push_back 之后发生竞争条件。


编辑:

好的,我重新考虑了这个问题并提出了新的解决方案。看看:

typedef boost::shared_ptr<boost::shared_mutex> MutexPtr;

struct FooSync {

    typedef boost::shared_ptr<FooSync> Ptr;

    FooSync(MutexPtr const& ptr) : mutex(ptr) {
    }

    void Wait() {
        mutex->lock_shared();
        mutex->unlock_shared();
    }

    MutexPtr mutex;

};

struct Foo {

    Foo() : mutex(new boost::shared_mutex) {
        mutex->lock();
    }

    ~Foo() {
        mutex->unlock();
    }

    FooSync::Ptr GetSync() {
        return FooSync::Ptr(new FooSync(mutex));
    }

    MutexPtr mutex;

};

现在看起来相当干净,并且更少的代码点受竞争条件的影响。在对象本身和所有同步对象之间只有一个同步原语共享。当Wait 在对象本身所在的线程中调用时,必须采取一些措施来克服这种情况(就像我的第一个示例一样)。如果目标平台不支持shared_mutex,可以使用good-ol mutex。当有很多FooSyncs 等待时,shared_mutex 似乎减轻了锁的负担。

【讨论】:

  • 如果线程 x 在线程 Y 处于破坏过程中调用 GetSync 会发生什么情况......坏事.. 但是我认为你走在正确的轨道上,只需要简化设计。让 Foo 在构造函数中获取 FooSync 互斥体并在析构函数中释放。任何等待 Foo 被破坏的线程都将阻塞在互斥体上,直到调用析构函数。由于 FooSync::wait() 然后立即调用 unlock 所有服务员将被释放。
  • @Charlie 关于您的第一点:我提到了一个已知限制-您无法在示例中的线程 x 中调用GetSync,因为断言将失败。这当然是不方便的,应该采取一些措施使其成为可能。
  • @Charlie 刚刚写了一个新的解决方案,请看一下。
  • 这看起来好多了,现在您需要注意的唯一“条件”是确保服务员不会在对象之前锁定互斥锁。应该通过强制使用 Foo:GetSync() 来避免这种情况
  • 以及您关于从同一线程调用 FooSync::wait 的评论。
猜你喜欢
  • 1970-01-01
  • 2018-03-31
  • 1970-01-01
  • 2015-03-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多