这是我目前正在上课的一个片段。它旨在为每个线程提供一个实例("类似单身")。这是一个常见的话题。 典型的实现使用由线程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是从多个线程读取的,并且可能是从另一个线程写入的,但从来没有一个有害的竞争条件。
或者我错过了一些重要的东西?
(当然,当销毁其数据的线程仍然在运行时,destroyThreadedInstance()不是线程安全的 - 我知道这一点。我也知道内存成本很高。)