I use a simple template class to provide thread local storage. This simply wraps a std::map
and a critical section. This then doesn't suffer from any platform specific thread local problems, the only platform requirement is to get the current thread id as in integer. It might be a little slower than native thread local storage but it can store any data type.
Below is a cut down version of my code. I have removed the the default value logic to simplify the code. As it can store any data type, the increment and decrement operators are only available if T
supports them. The critical section is only required to protect looking up and inserting into the map. Once a reference is returned it is safe to use unprotected as only the current thread will use this value.
template <class T>
class ThreadLocal
{
public:
operator T()
{
return value();
}
T & operator++()
{
return ++value();
}
T operator++(int)
{
return value()++;
}
T & operator--()
{
return --value();
}
T operator--(int)
{
return value()--;
}
T & operator=(const T& v)
{
return (value() = v);
}
private:
T & value()
{
LockGuard<CriticalSection> lock(m_cs);
return m_threadMap[Thread::getThreadID()];
}
CriticalSection m_cs;
std::map<int, T> m_threadMap;
};
To use this class I generally declare a static member inside a class eg
class DBConnection {
DBConnection() {
++m_connectionCount;
}
~DBConnection() {
--m_connectionCount;
}
// ...
static ThreadLocal<unsigned int> m_connectionCount;
};
ThreadLocal<unsigned int> DBConnection::m_connectionCount
It might not be perfect for every situation but it covers my need and I can easily add any features it is missing as I discover them.
bdonlan is correct this example doesn't clean up after threads exit. However this is very easy to add manually clean up.
template <class T>
class ThreadLocal
{
public:
static void cleanup(ThreadLocal<T> & tl)
{
LockGuard<CriticalSection> lock(m_cs);
tl.m_threadMap.erase(Thread::getThreadID());
}
class AutoCleanup {
public:
AutoCleanup(ThreadLocal<T> & tl) : m_tl(tl) {}
~AutoCleanup() {
cleanup(m_tl);
}
private:
ThreadLocal<T> m_tl
}
// ...
}
Then a thread that knows it makes explicit use of the ThreadLocal
can use ThreadLocal::AutoCleanup
in its main function to clean up the variable.
Or in the case of DBConnection
~DBConnection() {
if (--m_connectionCount == 0)
ThreadLocal<int>::cleanup(m_connectionCount);
}
The cleanup()
method is static so as not to interfere with operator T()
. A global function can be used to call this which would infer the Template parameters.