每个线程类的快速实例 - 它是否是线程安全的?

时间:2016-04-13 19:31:55

标签: c++ multithreading c++11 singleton

这是我目前正在上课的一个片段。它旨在为每个线程提供一个实例("类似单身")。这是一个常见的话题。 典型的实现使用由线程ID索引并保存数据的锁定映射。

我试过这个,但是由于我们在当前的项目中大量使用它,它占据了我们整个处理能力的3%。目前,重组不是一种选择 - 它的遗留代码被用于线程化。

所以我介绍了一个完全非锁定的静态数组,它由线程ID的低16位部分索引。只有在这些之间发生冲突(非常罕见)时,才会使用较慢和锁定的地图访问。

#pragma once
#include <array>
#include <map>
#include <cstdint>
#include <mutex>


namespace Base
{

    template<typename T> class InstancePerThread
    {
    public:
#if defined( _WIN32 )
        typedef uint32_t ThreadId;
#else
#error "Not implemented"
#endif
    private:
        T* const                                    INVALID = (T*)-1;
    protected:
        std::array<T*,UINT16_MAX+1>                 m_instancesArray;
        std::map<ThreadId,T*>                       m_instancesMap;
        std::recursive_mutex                        m_lock;
    public:
        InstancePerThread() { m_instancesArray.fill(nullptr); }
        virtual ~InstancePerThread() = default;

        T& getInstancePerThread()
        {
            const ThreadId currentThreadId = getCurrentThreadId();
            T* result = m_instancesArray[(uint16_t)currentThreadId];
            assert( result && "Instance not created!" );
            if( result != INVALID )
                return *result;

            {
                std::lock_guard<std::recursive_mutex> l(m_lock);

                assert( m_instancesMap.find(currentThreadId) != m_instancesMap.end() && "Instance not created!" );
                return *m_instancesMap[currentThreadId];
            }
        }

        void createThreadedInstance( const ThreadId _threadId, const std::function<T*()>& _new = []() { return new T() }, const std::function<void(T*)>& _init = nullptr )
        {
            {
                std::lock_guard<std::recursive_mutex> l(m_lock);


                assert( m_instancesMap.find(_threadId) == m_instancesMap.end() && "Instance already created!" );
                if( m_instancesMap.find(_threadId) == m_instancesMap.end() )
                {

                    T* t = _new();

                    // check for collision
                    T* alreadyExisting = m_instancesArray[(uint16_t)_threadId];
                    m_instancesArray[(uint16_t)_threadId] = alreadyExisting ? INVALID : t;

                    m_instancesMap[_threadId] = t;
                    if( _init )
                        _init(t);
                }

            }

        }

        void destroyThreadedInstance( const ThreadId _threadId, const std::function<void(T*)>& _destroy = nullptr, const std::function<void(T*)>& _delete = [](T* t) { delete t; }  )
        {
            {
                std::lock_guard<std::recursive_mutex> l(m_lock);
                assert( m_instancesMap.find(_threadId) != m_instancesMap.end() && "Instance not created!" );
                if( m_instancesMap.find(_threadId) != m_instancesMap.end() )
                {
                    T* t = m_instancesMap[_threadId];
                    if( _destroy )
                        _destroy( t );
                    _delete(t);

                    T* alreadyExisting = m_instancesArray[(uint16_t)_threadId];
                    if( alreadyExisting != INVALID )
                        m_instancesArray[(uint16_t)_threadId] = nullptr;

                    m_instancesLockMap.erase( _threadId );
                    m_instancesMap.erase( _threadId );
                }
            }

        }

        static inline ThreadId getCurrentThreadId()
        {
#if defined( _WIN32 )
            return GetCurrentThreadId();
#else
#error "Not implemented"
#endif
        }


    };

}

现在我的问题是:这真的是线程安全吗?

据我所知,尽管m_instancesArray是从多个线程读取的,并且可能是从另一个线程写入的,但从来没有一个有害的竞争条件。

  • 读取线程永远不会访问相同的索引
  • 书写线程永远不会改变容器大小/结构(它是一个静态数组)
  • 只有一次读访问:T * result = m_instancesArray [(uint16_t)currentThreadId];
  • 这应该总是返回一个有效的指针,这绝不会被写线程或INVALID无效,即使出现隐藏的竞争条件也是如此。

或者我错过了一些重要的东西?

(当然,当销毁其数据的线程仍然在运行时,destroyThreadedInstance()不是线程安全的 - 我知道这一点。我也知道内存成本很高。)

0 个答案:

没有答案