shared_ptr的删除程序是否执行任何同步?

Does shared_ptr's deleter do any synchronization?

本文关键字:执行 任何 同步 是否 删除程序 ptr shared      更新时间:2023-10-16

每个使用多线程环境的人都知道,您必须在线程之间同步以避免竞争情况。 我对shared_ptr删除器中发生的同步特别感兴趣。

在我的真实情况下,我有多个类交互,其中一些类知道同步正在进行,而另一些则不知道。 在这个例子中,我人为地将它们全部捆绑到一个对象上来阐明这个问题:

class TestObject
{
    public:
        TestObject()
        : mMarked(false)
        { }
        ~TestObject()
        {
            // use of mMarked here indicates that the destructor must be synchronized
            // with any thread that calls mark()
            std::cout << "Object " << (mMarked ? "was marked." : "was not marked.");
        }
        void mark()  { mMarked = true; }
        void someBehaviorThatDoesntNeedSynchronization();
    private:
        bool    mMarked;
};

thread 1:
std::shared_ptr<TestObject> objPtr1 = /* initialize to some instance */;
objPtr1->someBehaviorThatDoesntNeedSynchronization();
objPtr1.reset(); // may call the destructor
thread 2:
std::shared_ptr<TestObject> objPtr2 = /* initialize to the same instance */;
objPtr2->mark();
objPtr2.reset(); // may call the destructor

该规范似乎表明根本没有同步。 然而,这似乎很不体贴。 似乎线程 1 应该知道对象发生的所有同步,然后才能拥有调用析构函数的权限(如果在堆栈展开期间调用析构函数,这可能会很残酷(。

我错过了什么吗? 我知道shared_ptr的每个实现实际上都出于这个原因进行了同步,但我在规范中找不到任何内容来表明我可以信任它。

规范中是否有任何内容表明在调用删除程序之前将进行同步?

从参考:

所有成员函数(包括复制构造函数和复制赋值(都可以由不同shared_ptr实例上的多个线程调用,而无需额外的同步,即使这些实例是副本并共享同一对象的所有权也是如此。如果多个执行线程在不同步的情况下访问同一shared_ptr,并且其中任何一个访问使用 shared_ptr 的非常量成员函数,则会发生数据争用,原子函数的shared_ptr重载可用于防止数据争用。

所以std::shared_ptr的同步有限。 不能使用非const方法(包括析构函数(在多个线程中安全地访问shared_ptr的单个实例

但是,两个共享相同生存期且数据shared_ptr都可以安全地从不同的线程访问。

如何实现这一点的一个想法是,销毁会进行互锁递减,这会减少引用计数器返回它以原子方式减少的值。

然后,无论哪个~shared_ptr(或.reset()(将其减少到0删除对象。

如果我们假设objPtr1objPtr2从同一源正确构造(可能具有同步,或者在传递给工人之前在同一线程中(,并且当.reset()被称为时,除了这两个之外的所有其他shared_ptr都超出了范围,那么两个.reset()中的一个将破坏TestObject

现在,从技术上讲,mMarked的值是不同步的,并且当一个线程在另一个线程(可能(读取它之前以不同步的方式修改该值时,结果是未定义的行为。 这方面的一个实际示例是mMarked由两个 CPU 单独缓存。 在一种情况下,缓存的副本将被修改。 这与其他缓存不同步,然后后者碰巧在.reset()上销毁。

因此,简而言之,在多个线程中访问引用同一对象本身的不同shared_ptr是安全的,但对共享对象的访问不会同步。

当shared_ptr对象释放其在指针上的保留时,它会以原子方式递减引用计数。如果该引用计数现在为零,则发布shared_ptr是引用该对象的唯一实例,它调用删除程序。

因此,具有共享所有权的对象销毁由引用计数的原子操作同步。