Я использую простой класс шаблона для обеспечения локального хранилища потока. Это просто оборачивает std::map
и критический раздел. Тогда это не страдает от каких-либо локальных проблем потока для конкретной платформы, единственное требование к платформе - получить идентификатор текущего потока, как в целочисленном виде. Он может быть немного медленнее, чем локальное хранилище собственного потока, но может хранить данные любого типа.
Ниже приведена урезанная версия моего кода. Я удалил логику значений по умолчанию, чтобы упростить код. Поскольку он может хранить любой тип данных, операторы увеличения и уменьшения доступны только в том случае, если их поддерживает T
. Критический раздел требуется только для защиты поиска и вставки в карту. После возврата ссылки можно использовать незащищенную, поскольку только текущая нить будет использовать это значение.
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;
};
Чтобы использовать этот класс, я обычно объявляю статический член внутри класса, например
class DBConnection {
DBConnection() {
++m_connectionCount;
}
~DBConnection() {
--m_connectionCount;
}
// ...
static ThreadLocal<unsigned int> m_connectionCount;
};
ThreadLocal<unsigned int> DBConnection::m_connectionCount
Возможно, он не идеален для каждой ситуации, но покрывает мои потребности, и я могу легко добавить любые функции, которые отсутствуют, по мере их обнаружения.
bdonlan правильно, этот пример не очищается после выхода из потоков. Однако это очень легко добавить, чтобы очистить вручную.
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
}
// ...
}
Тогда поток, который знает, что явно использует ThreadLocal
, может использовать ThreadLocal::AutoCleanup
в своей основной функции для очистки переменной.
или в случае DBConnection
~DBConnection() {
if (--m_connectionCount == 0)
ThreadLocal<int>::cleanup(m_connectionCount);
}
Метод cleanup()
является статическим, чтобы не мешать operator T()
. Для вызова этой функции можно использовать глобальную функцию, которая выведет параметры шаблона.