【问题标题】:Creating a non-thread safe shared_ptr创建一个非线程安全的 shared_ptr
【发布时间】:2024-04-18 02:40:01
【问题描述】:

我正在开发一个多线程程序,但有一个 UI 组件广泛使用 std::shared_ptr 来管理元素。我可以保证只有一个线程会使用这些 shared_ptrs。

有没有办法定义一个不会产生线程安全引用计数开销的 shared_ptr?

它可以基于 boost::shared_ptr 或 std::shared_ptr。

编辑:感谢您提到 intrusive_ptr 的答案。我没有提到我还需要weak_ptr 功能以便排除它。

更新:我的答案是使用 Boost 的 local_shared_ptr。查看“他漫无目的”的评论

【问题讨论】:

  • 开销真的那么糟糕吗?
  • 同意。开销是如此之小,以至于它不应该成为瓶颈。如果您在某处遇到瓶颈,则它不在 shared_ptr 中。
  • @inestical:对于这种情况,您可能是对的,但不要忘记 shared_ptr 在堆上分配它的引用计数。这样做一百万次可能会导致严重的开销。例如*.com/questions/3628081/shared-ptr-horrible-speed
  • 这就是make_shared 的用途。
  • 为了记录,现在 boost 有local_shared_ptr,它没有线程安全保证,也可以和weak_ptr一起工作。

标签: c++ boost stl


【解决方案1】:

Andrei Alexandrescu 在 CppCon 2014 上谈到了实现您自己的单线程共享指针类(带有一些额外的优化)

观看视频here

还有幻灯片here

我真的认为标准或提升应该提供一个模板参数,以便在他们的共享 ptrs 中使用原子引用计数......

【讨论】:

    【解决方案2】:

    您可以使用intrusive_ptr,因为它允许您提供自己的引用计数。如果该引用计数是变量的简单递增/递减,那么您可能不会获得比这更好的性能。

    【讨论】:

    • 谢谢,但我也需要weak_ptr功能
    【解决方案3】:

    我的代码复制shared_ptr 的开销已成为问题,并且当时使用了替代技术。让我首先证明其他 cmets 是正确的,shared_ptr 的开销非常低。我对此进行了分析,以实际找到我的麻烦点之一。在我的 AMD64 Phenom 上,调用复制 shared_ptr 的函数大约需要 12ns,而使用普通指针调用相同的函数大约需要 1ns。

    考虑到这些数字,很难想象您会在原始指针和shared_ptr 之间获得任何类型的“安全”变体。因此,在这种情况下,我将实际指针或const & 传递给shared_ptr。通常我会在整个代码段上放置一个互斥锁,这样我就可以保证在整个持续时间内保持 shared_ptr 。您可以手动滚动单线程引用计数,但如果您知道它没有被共享,那又有什么意义呢?

    但要非常仔细地考虑时间安排。除非您每秒复制 shared_ptr 数千甚至数万次,否则您不会注意到 shared_ptr 的开销。

    在同一个项目的 GUI 代码中,我总是使用 shared_ptr,只有服务器代码在几个关键区域避免了它。 GUI 中还有很多其他的东西会减慢它的速度:避免 shared_ptr 不会有明显的区别。

    【讨论】:

    • 是的,我确实有一种情况,成千上万的 shared_ptr 可能会被复制,因此有兴趣删除线程安全
    【解决方案4】:

    我建议使用 Boost 侵入式智能指针。

    还有一个来自 Scott Meyer(这里:http://www.aristeia.com/BookErrata/M29Source.html)的实现,发表在“More Effective C++”中

    但是,如果它有帮助,我拉了一个简单的引用计数指针(对多态赋值的一些支持和自定义删除器)。 这个是非线程感知的

    注意:我记错了。多态分配是另一个项目的变体。我也有,但它不支持自定义删除器:) 如果有人感兴趣,请告诉我;当然,它带有单独的功能单元测试

    它带有单元测试(例如检查著名的remove linked list node 排序错误)。所以你知道你得到了什么:)

    /*
     * counted_ptr - simple reference counted pointer.
     *
     * The is a non-intrusive implementation that allocates an additional
     * int and pointer for every counted object.
     */
    #ifndef COUNTED_PTR_H
    #define COUNTED_PTR_H
    
    #include <stdlib.h>
    
    extern "C" bool mtx_unit_test_countedptr();
    
    namespace MtxChess {
    
    /* For ANSI-challenged compilers, you may want to #define
     * NO_MEMBER_TEMPLATES or explicit */
    
    template <class X>
        struct FreeMallocPolicy
        {
            static void do_free(X* p) { if (p) ::free(p); p = 0; }
        };
    
    template <class X>
        struct ScalarDeletePolicy
        {
            static void do_free(X* p) { if (p) delete p; p = 0; }
        };
    
    template <class X>
        struct ArrayDeletePolicy
        {
            static void do_free(X* p) { if (p) delete[] p; p = 0; }
        };
    
    template <class X,class _P=ScalarDeletePolicy<X> > class counted_ptr
    {
    public:
        typedef X element_type;
    
        explicit counted_ptr(X* p = 0) // allocate a new counter
            : itsCounter(0) {if (p) itsCounter = new counter(p);}
        ~counted_ptr()
            {release();}
        counted_ptr(const counted_ptr& r) throw()
            {acquire(r.itsCounter);}
        operator bool() const { return 0!=get(); }
        void clear() { (*this) = counted_ptr<X>(0); }
        counted_ptr& operator=(const counted_ptr& r)
        {
            if (this != &r) {
                auto_release keep(itsCounter);
                acquire(r.itsCounter);
            }
            return *this;
        }
        bool operator<(const counted_ptr& r) const
        {
            return get()<r.get();
        }
        bool operator==(const counted_ptr& r) const
        {
            return get()==r.get();
        }
        bool operator!=(const counted_ptr& r) const
        {
            return get()!=r.get();
        }
    
    #ifndef NO_MEMBER_TEMPLATES
    //  template <class Y> friend class counted_ptr<Y>;
        template <class Y> counted_ptr(const counted_ptr<Y>& r) throw()
            {acquire(r.itsCounter);}
        template <class Y> counted_ptr& operator=(const counted_ptr<Y>& r)
        {
            if (this != &r) {
                auto_release keep(itsCounter);
                acquire(r.itsCounter);
            }
            return *this;
        }
        template <class Y> bool operator<(const counted_ptr<Y>& r) const
        {
            return get()<r.get();
        }
        template <class Y> bool operator==(const counted_ptr<Y>& r) const
        {
            return get()==r.get();
        }
        template <class Y> bool operator!=(const counted_ptr<Y>& r) const
        {
            return get()!=r.get();
        }
    #endif // NO_MEMBER_TEMPLATES
    
        X& operator*()  const throw()   {return *itsCounter->ptr;}
        X* operator->() const throw()   {return itsCounter->ptr;}
        X* get()        const throw()   {return itsCounter ? itsCounter->ptr : 0;}
        bool unique()   const throw()
            {return (itsCounter ? itsCounter->count == 1 : true);}
    
    private:
        struct counter {
            counter(X* p = 0, unsigned c = 1) : ptr(p), count(c) {}
            X*          ptr;
            unsigned    count;
        }* itsCounter;
    
        void acquire(counter* c) throw()
        {   
            // increment the count
            itsCounter = c;
            if (c) ++c->count;
        }
    
        void release()
        {   
            dorelease(itsCounter);
        }
    
        struct auto_release
        {
            auto_release(counter* c) : _c(c) {}
           ~auto_release() { dorelease(_c); }
            counter* _c;
        };
    
        void static dorelease(counter* itsCounter)
        {
            // decrement the count, delete if it is 0
            if (itsCounter) {
                if (--itsCounter->count == 0) {
                    _P::do_free(itsCounter->ptr);
                    delete itsCounter;
                }
                itsCounter = 0;
            }
        }
    };
    
    } // EON
    
    #endif // COUNTED_PTR_H
    

    单元测试(独立编译)

    /*
     * counted_ptr (cpp) - simple reference counted pointer.
     *
     * The is a non-intrusive implementation that allocates an additional
     * int and pointer for every counted object.
     */
    
    #include "counted_ptr.hpp"
    #include "internal.hpp"
    #include <map>
    #include <string>
    
    namespace MtxChess { 
    
        namespace /*anon*/
        {
            // sensed events
            typedef std::map<std::string, int> Events;
            static Events constructions, destructions;
    
            struct Trackable
            {
                Trackable(const std::string& id) : _id(id) {    constructions[_id]++; }
                ~Trackable()                               {    destructions[_id]++; }
                const std::string _id;
            };
    
            typedef counted_ptr<Trackable> target_t;
    
            bool testBehaviour()
            {   
                static const counted_ptr<Trackable> Nil = target_t(0);
                bool ok = true;
    
                constructions.clear();
                destructions.clear();
    
                MTXASSERT_EQ(ok, 0ul,  constructions.size());
                MTXASSERT_EQ(ok, 0ul,  destructions.size());
    
                target_t a = target_t(new Trackable("aap"));
    
                MTXASSERT_EQ(ok, 1ul,  constructions.size());
                MTXASSERT_EQ(ok, 1,    constructions["aap"]);
                MTXASSERT_EQ(ok, 0ul,  destructions.size());
    
                MTXASSERT_EQ(ok, 0,    constructions["noot"]);
                MTXASSERT_EQ(ok, 2ul,  constructions.size());
                MTXASSERT_EQ(ok, 0ul,  destructions.size());
    
                target_t hold;
                {
                    target_t b = target_t(new Trackable("noot")),
                             c = target_t(new Trackable("mies")),
                             nil = Nil,
                             a2 = a;
    
                    MTXASSERT(ok, a2==a);
                    MTXASSERT(ok, nil!=a);
    
                    MTXASSERT_EQ(ok, 3ul,  constructions.size());
                    MTXASSERT_EQ(ok, 1,    constructions["aap"]);
                    MTXASSERT_EQ(ok, 1,    constructions["noot"]);
                    MTXASSERT_EQ(ok, 1,    constructions["mies"]);
                    MTXASSERT_EQ(ok, 0,    constructions["broer"]);
                    MTXASSERT_EQ(ok, 4ul,  constructions.size());
    
                    MTXASSERT_EQ(ok, 0ul,  destructions.size());
    
                    hold = b;
                }
    
                MTXASSERT_EQ(ok, 1ul,  destructions.size());
                MTXASSERT_EQ(ok, 0,    destructions["aap"]);
                MTXASSERT_EQ(ok, 0,    destructions["noot"]);
                MTXASSERT_EQ(ok, 1,    destructions["mies"]);
                MTXASSERT_EQ(ok, 3ul,  destructions.size());
    
                hold = Nil;
                MTXASSERT_EQ(ok, 3ul,  destructions.size());
                MTXASSERT_EQ(ok, 0,    destructions["aap"]);
                MTXASSERT_EQ(ok, 1,    destructions["noot"]);
                MTXASSERT_EQ(ok, 1,    destructions["mies"]);
                MTXASSERT_EQ(ok, 4ul,  constructions.size());
    
                // ok, enuf for now
                return ok;
            }
    
            struct Linked : Trackable
            {
                Linked(const std::string&t):Trackable(t){}
                counted_ptr<Linked> next;
            };
    
            bool testLinked()
            {   
                bool ok = true;
    
                constructions.clear();
                destructions.clear();
                MTXASSERT_EQ(ok, 0ul,  constructions.size());
                MTXASSERT_EQ(ok, 0ul,  destructions.size());
    
                counted_ptr<Linked> node(new Linked("parent"));
                MTXASSERT(ok, node.get()); 
                node->next = counted_ptr<Linked>(new Linked("child"));
    
                MTXASSERT_EQ(ok, 2ul,  constructions.size());
                MTXASSERT_EQ(ok, 0ul,  destructions.size());
    
                node = node->next;
                MTXASSERT(ok, node.get()); 
    
                MTXASSERT_EQ(ok, 2ul,  constructions.size());
                MTXASSERT_EQ(ok, 1ul,  destructions.size());
    
                node = node->next;
                MTXASSERT(ok,!node.get()); 
    
                MTXASSERT_EQ(ok, 2ul,  constructions.size());
                MTXASSERT_EQ(ok, 2ul,  destructions.size());
    
                return ok;
            }
    
        }
    
    } // EON
    
    int main()
    {   
        using namespace MtxChess;
    
        bool ok = true;
        ok = testBehaviour() && ok;
        ok = testLinked() && ok;
    
        return ok?0:1;
    }
    

    【讨论】:

    • 谢谢,但我也需要weak_ptr功能
    【解决方案5】:

    Boost 提供了一个可以定义的宏,它不会使用线程安全的引用计数。

    【讨论】:

    • 这不是构建时间吗?即使用非线程安全的引用计数构建 boost 库。我需要在同一代码库中的其他地方进行线程安全引用计数。
    【解决方案6】:

    现在将此添加为已接受的答案。 Boost local_shared_ptr 是一个单线程引用计数的智能指针,它使用非原子操作来提高速度:

    https://www.boost.org/doc/libs/1_65_0/libs/smart_ptr/doc/html/smart_ptr.html#local_shared_ptr

    【讨论】:

    • local_shared_ptr 看起来棒极了,但我不明白这与weak_ptr 是如何一起工作的?似乎在 local_shared_ptr 周围创建一个 week_ptr 然后调用 lock() 将返回一个实际的 shared_ptr,而不是 local_shared_ptr ......因此如果大多数用途是weak_ptrs,则可能会否定 local_shared_ptr 的任何好处。我在这里错过了什么?
    • 好点!在我的代码中,弱指针仅偶尔使用,lock() 中的 shared_ptr 仅在一个函数的范围内使用。正如您指出的那样,如果您的代码广泛使用 weak_ptr 然后保留 lock() 的结果,那么好处就会丧失。希望他们能添加一个 local_weak_ptr。