Критические секции и синглтон - PullRequest
2 голосов
/ 02 января 2012

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

Мой вопрос таков: если вы оберните инициализации статических переменных в критическом разделе, предотвратит ли это двойную инициализацию? Пример:

CRITICAL_SECTION cs;

Class get_class_instance() {
    EnterCriticalSection(&cs);

    // is the initialisation of c done inside the critical section?
    static Class c = Class(data);

    LeaveCriticalSection(&cs);

    return c;
}

Или инициализация выполняется магическим образом (не в точке объявления / инициализации), как инициализация элемента переменных перед началом конструктора?

Мой вопрос касается, в частности, пре-C ++ 11, поскольку, согласно ответу Xeo, C ++ 11 сам позаботится об этом.

Ответы [ 4 ]

5 голосов
/ 02 января 2012

C ++ 11 устраняет необходимость в блокировке. Параллельное выполнение должно ожидать, если статическая локальная переменная уже инициализируется.

§6.7 [stmt.dcl] p4

Если элемент управления вводит объявление одновременно во время инициализации переменной, параллельное выполнение должно ожидать завершения инициализации.


Для C ++ 03 у нас есть это:

§6.7 [stmt.dcl] p4

Инициализация нуля (8.5) всех локальных объектов со статической продолжительностью хранения (3.7.1) выполняется перед любой другой инициализацией. Локальный объект типа POD (3.9) со статической продолжительностью хранения, инициализированной с помощью константных выражений, инициализируется перед первым вводом его блока. Реализация может выполнять раннюю инициализацию других локальных объектов со статической продолжительностью хранения при тех же условиях, что и реализация допускает статическую инициализацию объекта со статической продолжительностью хранения в области пространства имен (3.6.2). В противном случае такой объект инициализируется при первом прохождении управления через его объявление ;

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

Так что с теоретической точки зрения ваш код должен быть безопасным.

Теперь это можно улучшить, чтобы не вводить критическую секцию при каждом вызове функции. Основная идея @Chethan - это звук, поэтому мы будем основываться на этом. Однако мы также собираемся избегать динамического распределения. Однако для этого мы полагаемся на Boost.Optional:

#include <boost/optional.hpp>

Class& get_class_instance() {
    static boost::optional<Class> c;
    static bool inited;

    if (!inited){
        EnterCriticalSection(&cs);

        if(!c)
            c = Class(data);

        LeaveCriticalSection(&cs);
        inited = true;
    }

    return *c;
}

Boost.Optional избегает инициализации по умолчанию, а двойная проверка избегает ввода критической секции при каждом вызове функции. Эта версия, однако, вводит конструктор копирования Class в назначении. Решением для этого являются фабрики на месте:

#include <boost/utility/in_place_factory.hpp>
#include <boost/optional.hpp>

Class& get_class_instance() {
    static boost::optional<Class> c;
    static bool inited;

    if (!inited){
        EnterCriticalSection(&cs);

        if(!c)
            c = boost::in_place(data);

        LeaveCriticalSection(&cs);
        inited = true;
    }

    return *c;
}

Я благодарю @R. Мартиньо Фернандес и @Ben Voigt, которые сотрудничали в этом окончательном решении. Если вы заинтересованы в этом процессе, не стесняйтесь взглянуть на стенограмму .


Теперь, если ваш компилятор уже поддерживает некоторые функции C ++ 11, но не статическую инициализацию, вы также можете использовать std::unique_ptr в сочетании с размещением нового и статически выровненным буфером:

#include <memory> // std::unique_ptr
#include <type_traits> // alignment stuff

template<class T>
struct destructor{
    void operator(T* p) const{
    if(p) // don't destruct a null pointer
        p->~T();
    }
};

Class& get_class_instance() {
    typedef std::aligned_storage<sizeof(Class),
        std::alignment_of<Class>::value>::type storage_type;
    static storage_type buf;
    static std::unique_ptr<Class, destructor> p;
    static bool inited;

    if (!inited){
        EnterCriticalSection(&cs);

        if(!p)
            p.reset(new (&buf[0]) Class(data));

        LeaveCriticalSection(&cs);
        inited = true;
    }

    return *p;
}
2 голосов
/ 03 января 2012

Для C ++ 03 вам необходимо без блокировки обновить состояние. Это связано с тем, что вы не можете использовать блокировку без ее инициализации, что приведет к повторной рекурсивной повторению той же самой проблемы, поскольку вам нужно поточно-инициализировать блокировку, которую вы собираетесь использовать для поточно-безопасной инициализации. Упс. Вы можете положиться только на нулевую инициализацию для глобалов и некоторых безблокировочных инструкций. Это также намного быстрее.

Эту проблему можно решить с помощью переменной состояния и инструкции CAS без блокировки.

enum State {
    ObjectUninitialized,
    ObjectInitializing,
    ObjectInitialized
};

volatile std::size_t state; // Must be initialized to 0- done by compiler
                            // before main(). All globals are 0
                            // Also must be word sized

T* func() {
    static char buffer[sizeof(T)];
    long result = InterlockedCompareExchange(&state, ObjectInitializing, ObjectUninitialized);
    if (result == ObjectInitialized) {
        return reinterpret_cast<T*>(buffer);
    }
    if (result == ObjectInitializing) {
        while (state == ObjectInitializing); // read is atomic for word-size variables
        return reinterpret_cast<T*>(buffer); // and use volatile to force compiler to add     
    }
    if (result == ObjectUninitialized) {
        new (buffer) T();
        InterlockedExchange(&state, ObjectInitialized);
        return reinterpret_cast<T*>(buffer);
    }
}
2 голосов
/ 02 января 2012

Завершение инициализации с критическими разделами, безусловно, поможет! Я бы использовал приведенный ниже код, чтобы убедиться, что наша статическая переменная инициализируется только один раз.

CRITICAL_SECTION cs;

Class& get_class_instance() {
    static Class *c; //by default, global and static variables are assigned default values. Hence c will be NULL when the program starts.

    EnterCriticalSection(&cs);

    if(c == NULL)
        c = new Class(data);

    LeaveCriticalSection(&cs);

    return *c;
}
0 голосов
/ 02 января 2012

Я не уверен, что ваш код определенно правильный, но в целом да, это решило бы проблему.

...