2009-08-04 8 views
4

Я хотел бы создать одноэлементный класс, который создается один раз в каждом потоке, где он используется. Я хотел бы сохранить указатели экземпляра в слотах TLS. Я придумал следующее решение, но я не уверен, есть ли какие-либо особые соображения при многопоточном доступе к фабрике singelton, когда речь идет о локальном хранилище потоков. Возможно, есть и лучшее решение для реализации потоковых локальных синглетонов.Thread-local singletons

class ThreadLocalSingleton 
{ 
    static DWORD tlsIndex; 
public: 
    static ThreadLocalSingleton *getInstance() 
    { 
     ThreadLocalSingleton *instance = 
      static_cast<ThreadLocalSingleton*>(TlsGetValue(tlsIndex)); 
     if (!instance) { 
      instance = new ThreadLocalSingleton(); 
      TlsSetValue(tlsIndex, instance); 
     } 
     return instance; 
    } 
}; 
DWORD ThreadLocalSingleton::tlsIndex = TlsAlloc(); 

TLS * -функции, конечно, специфичны для Win32, но переносимость здесь не является основной проблемой. Ваши мысли о других платформах будут по-прежнему ценными.

Major Редактировать: Первоначально я спросил об использовании блокировки с двойной проверкой в ​​этом сценарии. Однако, как указывал DavidK, синглтоны должны создаваться на основе каждой нити.

Две оставшиеся вопросы:

  1. уместно ответить на TlsGetValue/TlsSetValue, чтобы гарантировать, что каждый поток получает один экземпляр и экземпляр создается только один раз для каждого потока?

  2. Возможно ли зарегистрировать обратный вызов, который позволяет мне очистить экземпляр, связанный с конкретным потоком, когда этот поток заканчивается?

ответ

11

Поскольку ваши объекты являются нитками, зачем вам блокировать, чтобы защитить их вообще? Каждый поток, который вызывает getInstance(), не зависит от какого-либо другого потока, поэтому почему бы просто не проверить, существует ли синглтон и создать его, если необходимо? Блокировка требовалась бы только в том случае, если несколько потоков пытались получить доступ к одному синглтону, что невозможно в вашем дизайне, поскольку оно выше.

EDIT: Переходя к двум другим вопросам ... Я не вижу причин, по которым использование TlsAlloc/TlsGetValue и т. Д. Не будет работать так, как вы ожидали. Поскольку память, содержащая указатель на ваш синглтон, доступна только для соответствующего потока, проблем с ленивой инициализацией не будет. Однако нет явного интерфейса обратного вызова, чтобы очистить их.

Очевидным решением для этого было бы иметь метод, который вызывается всеми основными функциями потока, который очищает созданный синглтон, если таковой имеется.

Если очень вероятно, что поток создаст синглтон, более простой шаблон может состоять в том, чтобы создать синглтон в начале основной функции потока и удалить его в конце. Затем вы можете использовать RAII, либо создав синглтон в стеке, либо удерживая его в std :: auto_ptr <>, чтобы он удалялся при завершении потока. (Если поток не завершится ненормально, но если это произойдет, все ставки будут отключены, а просочившийся объект станет наименьшей из ваших проблем.) Затем вы можете просто передать синглтон или сохранить его в TLS или сохранить его в члене класс, если большая часть функций потока находится в одном классе.

+0

Ну спасибо. Я был явно в слишком сложном режиме. – VoidPointer

4

Посмотрите this paper, чтобы понять, почему перепроверены замок не работает вообще (даже если оно может работать в особых случаях).

+0

Ключевое слово volatile должно исправить эту проблему в компиляторе microsoft с VS2005. См. Http://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Microsoft_Visual_C.2B.2B – VoidPointer

+2

Однако, что касается использования 'volatile' для этого, VC _is_ является особым случаем. В общем, это не сработает. – sbi

+1

Правильно ... ссылка на эту статью всегда в порядке, когда обсуждается DCLP :) – VoidPointer

1

Мы используем класс, который хранит карту идентификатора потока для данных для реализации нашего локального хранилища потоков. Кажется, что это работает очень хорошо, тогда экземпляр этого класса можно разместить везде, где требуется локальное хранилище потоков. Обычно клиенты используют экземпляр как статическое частное поле.

Вот грубый набросок кода

template <class T> 
struct ThreadLocal { 
    T & value() 
    { 
     LockGuard<CriticalSection> lock(m_cs); 

     std::map<int, T>::iterator itr = m_threadMap.find(Thread::getThreadID()); 

     if(itr != m_threadMap.end()) 
      return itr->second; 

     return m_threadMap.insert(
      std::map<int, T>::value_type(BWThread::getThreadID(), T())) 
       .first->second; 
    } 

    CriticalSection  m_cs; 
    std::map<int, T> m_threadMap; 
}; 

Это затем используется в качестве

class A { 
    // ... 

    void doStuff(); 
private: 
    static ThreadLocal<Foo> threadLocalFoo; 
}; 

ThreadLocal<Foo> A::threadLocalFoo; 

void A::doStuff() { 
    // ... 
    threadLocalFoo.value().bar(); 
    // ... 
} 

Это очень просто и работает на любой платформе, где вы можете получить идентификатор потока. Обратите внимание, что критическая секция используется только для возврата/создания ссылки, как только у вас есть ссылка, все вызовы находятся за пределами критической секции.

+1

Я тоже использовал этот подход, и он отлично работает. Но недостатком является то, что он относительно медленный из-за блокировки и доступа к карте. Итак, теперь я использую предыдущий статический массив, индексированный идентификатором потока (отбрасывается до 16 бит), что намного быстрее, потому что нет необходимости блокировать его. Только если происходит столкновение между двумя литыми идентификаторами потоков, используется карта. Конечно, это решение также имеет недостаток - он очень тяжелый для памяти (дополнительно 1/4 Мбайт на синглтон). – user2328447

 Смежные вопросы

  • Нет связанных вопросов^_^