【问题标题】:Delete an object securely from a multi-threaded program从多线程程序中安全删除对象
【发布时间】:2012-09-17 08:07:11
【问题描述】:

免责声明:既不允许 Boost,也不允许 C++11。

我有一个程序,我在其中创建了一个Foo 的实例,并在多个线程中对其进行操作。然后我想安全地删除它,这样那些线程就不会陷入分段错误。

我已向Foo 添加了一个互斥成员,并在每次线程函数运行时锁定它。为了不同的线程不会相互冲突。

class Foo
{
    public:
        pthread_mutex_t mutex;
};

void* thread ( void* fooPtr )
{
    Foo* fooPointer = (Foo*) fooPtr;

    while ( 1 )
    {
        if ( fooPointer )
        {
            pthread_mutex_lock ( &fooPointer->mutex );
            /* some code, involving fooPointer */
            pthread_mutex_unlock ( &fooPointer->mutex );
        }
        else
        {
            pthread_exit ( NULL );
        }
    }
}

现在我想安全地删除foo,这样线程中就不会发生错误。我给Foo添加了一个析构函数:

Foo::~Foo()
{
    pthread_mutex_lock ( &mutex );
}

现在对象不会被删除,直到所有线程完成当前循环。

问题是:删除实例后互斥锁会被解锁吗?删除实例后所有线程都会结束吗?我敢打赌答案是no。所以我改变了析构函数,但现在它似乎是线程不安全的:

Foo::~Foo()
{
    pthread_mutex_lock ( &mutex );
    pthread_mutex_unlock ( &mutex );
}

线程函数能否在pthread_mutex_unlock ( &mutex );之后和对象被删除之前锁定互斥体并开始操作实例?

【问题讨论】:

  • 您正在将Foo 传递给您的线程函数,因此,为什么不在所有线程完成运行后简单地删除对象呢?如果不是,您应该编写自己的shared_ptr 版本,如上所述
  • 或者只是...复制该死的标题。 boost 许可证允许您这样做(稍微不那么重要:RAII 是否也不允许?将其用于锁防护)
  • 你的 if(fooPointer) 永远不会工作。没有人会因为 foo 对象被删除而将您的 fooPointer 设置为 NULL。如果您的应用程序确实需要线程之间的这种通信,您可以为此目的使用弱指针。
  • 因为你已经围绕一个误解构建了一个解决方案,并且仅仅考虑到你做了什么,不可能推断出你应该做什么。
  • 解决方案是自己实现 boost::shared_ptr 和 boost::weak_ptr !你的 fooPointer 需要是一个弱指针,故事结束。

标签: c++ multithreading thread-safety mutex delete-operator


【解决方案1】:

让我们从你的问题开始:

我有一个程序,我在其中创建了一个 Foo 的实例并进行操作 在许多线程中使用它。然后我想安全地删除它 这些线程不会陷入分段错误。

您不能删除正在使用的对象。再多的互斥锁也无法解决这个问题。

我给 Foo 添加了一个析构函数

这仅在删除Foo 时运行。不过,它的内容并不重要:在其他线程仍在使用 Foo 时调用 dtor 是错误的。

我希望在删除实例时线程安全退出。怎么可能?

嗯,这是正确的问题。我可以编写很多代码供您使用,但这些代码只是boost::weak_ptr 的副本。所以,我不会打扰。只需自己获取升压代码。

不允许提升。

那你为什么要在 StackOverflow 上提问?这本质上是相同的许可证。

【讨论】:

  • +1 引用 Then why are you asking on StackOverflow? That's essentially the same license.
  • @Kolyunya:而你仍然说不允许 Boost,即使是想法也不行?
  • TRWTF 是“允许用于想法,而不是用于代码”。你能拼写“NIH 综合症”吗
  • @Kolyunya:认真的吗?你会走多远 - 你知道(或关心)pthread_mutex_t 是如何实现的吗?
  • @Kolyunya:您了解供应商std::string 实现中的小字符串优化吗?用于优化某些标准库类型的内存要求的空基类技巧?不?也这么觉得。现在,您的反对意见又是什么?
【解决方案2】:

这里缺少的是指示线程处理何时完成的条件。删除特定对象实例不是一个好的条件。您还没有向我们展示删除对象的位置。如果我们可以在代码中看到这一点,那么额外的上下文将会很有帮助。

我建议不要删除对象,而是在对象上设置一个标志(例如 bool active())。然后所有线程都会检查这个标志,当它指示停止处理时,线程将停止。在当前删除 Foo 对象的位置设置此标志。然后一旦所有线程都停止,删除 Foo 对象。

如果您删除对象并期望能够获得它的互斥锁,您可能会遇到崩溃,或者至少是不稳定的行为,因为互斥锁是 Foo 的成员,并且它将与对象一起被销毁.

这是我的意思的一个例子:

class Foo
{
    public:
        void lockMutex();
        void unlockMutex();
        // Active should be mutex protected as well
        // Or you could consider using a pthread_rwlock
        bool active() {return active_;}
        void setActive(bool a) {active_ = a;}
    private:
        pthread_mutex_t mutex;
        bool active_;
};

void* thread ( void* fooPtr )
{
    Foo* fooPointer = (Foo*) fooPtr;

    while ( 1 )
    {
        if ( fooPointer->active() )
        {
            fooPointer->lockMutex();
            /* some code, involving fooPointer */
            fooPointer->unlockMutex();
        }
        else
        {
            pthread_exit ( NULL );
        }
    }

    // somewhere else in the code
    fooPointer->setActive(false);
}

Foo::setActive(true) 必须在构造函数中调用,或者在创建对象时调用。一旦线程停止,应该删除 Foo 对象,很可能是在 pthread_join() 完成之后。

【讨论】:

  • 访问active 需要某种方式来保证更新的可见性。
  • @R.MartinhoFernandes,好点,它也应该受到互斥体保护。
  • 这最终不是正确的答案。该代码只是降低了崩溃的比率。考虑这种情况:类实例被破坏,但由于某种原因另一个仍在访问 fooPointer。
【解决方案3】:

您发布的代码不正确,因为 c++ 对象已被步骤破坏:

obj->Foo::~Foo();

free memory //maybe delete if allocated by new

所以你的源代码只是保护析构函数而不是释放内存本身。

也许下面的源代码可以帮助你,它简单粗暴,但我认为它可以工作

    class Foo 
    {

       public:
         void dosomething() {}
    };

    template<typename T>
    class Protect
    {   
    public: 
        struct auto_lock {
            auto_lock(pthread_mutex_t& mutex)
                : _mutex(mutex)
            {
                pthread_mutex_lock ( &_mutex );
            }
            ~ auto_lock() 
            {
                pthread_mutex_unlock ( &_mutex );
            }
            pthread_mutex_t& _mutex;
        };

        Protect(T*& p): _p(p) {}
        T* get() { return _p; }

        void lock() { pthread_mutex_lock ( &_mutex ); }
        void unlock() { pthread_mutex_unlock ( &_mutex );}
        pthread_mutex_t& getlock() { return _mutex; }

        void safe_release() { 
            auto_lock l(_mutex);
            if (_p != NULL)  {
                delete _p;
                _p = NULL;
            }
        }
    private:
        T*& _p;
        pthread_mutex_t _mutex;
    };

void* thread ( void* proPtr )
{
    Protect<Foo>* proPointer = (Protect<Foo>*) proPtr;

    while ( 1 )
    {
        Protect<Foo>::auto_lock l(proPointer->getlock());
        Foo* fooPtr = proPointer->get();
        if ( fooPtr )
        {
            fooPtr->dosomething();
        }
        else
        {
            pthread_exit ( NULL );
        }
    }
}

【讨论】:

  • (不是我的反对意见)。你的假设是错误的。当可以运行 dtor 时,这是因为没有其他用户使用该对象,因此没有其他用户使用内存。这意味着没有必要“保护”内存释放本身。
  • 我的回答是错误的,我想我没有完全理解整个事情。谢谢,MSalters。
猜你喜欢
  • 1970-01-01
  • 2015-10-26
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-01-15
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多