Локальные синглтоны - PullRequest
       28

Локальные синглтоны

4 голосов
/ 04 августа 2009

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

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 Edit : Первоначально я спрашивал об использовании двойной проверки блокировки в этом сценарии. Однако, как указывало DavidK , синглтоны в любом случае должны создаваться для каждого потока.

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

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

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

Ответы [ 3 ]

11 голосов
/ 04 августа 2009

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

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

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

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

4 голосов
/ 04 августа 2009

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

1 голос
/ 04 августа 2009

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

Вот примерный набросок кода

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();
    // ...
}

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

...