Это действительная альтернатива двойной проверке блокировки? - PullRequest
0 голосов
/ 30 января 2012

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

Я пишу следующий код, надеясь, что он работает как альтернатива блокировке с двойной проверкой.Это правильная реализация поточно-ориентированного одноэлементного шаблона?

static bool created = false;
static Instance *instance = 0;

Instance *GetInstance() {
    if (!created) {
        Lock lock;    // acquire a lock, parameters are omitted for simplicity
        if (!instance) {
            instance = new Instance;
        } else {
            created = true;
        }
    }
    return instance;
}

Первый вызов создаст экземпляр.Второй вызов установит значение true.И, наконец, все другие вызовы вернут хорошо инициализированный экземпляр.

http://voofie.com/content/192/alternative-to-double-checked-locking-and-the-singleton-pattern/

Ответы [ 5 ]

3 голосов
/ 30 января 2012

Нет, это не помогает.Если записи в created и instance не атомарны, то нет никакой гарантии, что значения видны потоку, который не блокирует мьютекс.

, например, поток 1 вызывает getInstance.created равно false, а instance равно нулю, поэтому он блокирует мьютекс и создает новый экземпляр.Поток 1 снова вызывает getInstance, и на этот раз для created устанавливается true.Тема 2 теперь вызывает getInstance.По капризам управления памятью процессора он видит created как true, но нет никакой гарантии, что он также видит instance как ненулевое значение, и даже если это так, нет гарантии, что значения памяти дляуказываемые экземпляры непротиворечивы.

Если вы не используете атомикс, вам нужно использовать мьютексы и использовать их для всех обращений к защищенной переменной.

Дополнительная информация: Если выиспользуют мьютексы, тогда компилятор и среда выполнения работают вместе, чтобы гарантировать, что когда один поток снимает блокировку мьютекса, а другой поток получает блокировку этого же мьютекса, тогда второй поток может видеть все записи, выполненные первой.Это не верно для неатомарных обращений и может или не может быть правдой для атомарных доступов, в зависимости от того, какие ограничения порядка памяти компилятор и гарантия времени выполнения для вас (с атомарностью C ++ 11 вы можете выбрать ограничения порядка).

3 голосов
/ 30 января 2012

Он имеет ту же надежность, что и двойная проверка блокировки. Вы можете получить больше с помощью «тройной проверки» или даже «четверной проверки», но полная надежность может оказаться невозможной.

Обратите внимание, что объявление локальной статической переменной заставит компилятор реализовать саму вашу логику.

#include<memory>
#include "Instance.h" //or whatever...

Instance* GetInstance()
{
    static std::unique_ptr<Instance> p(new Instance);
    return p.get();
}

Если компилятор настроен на многопоточную среду, он может защитить статический p с помощью мьютекса и управлять блокировкой при инициализации p при самом первом вызове. Он также должен связать уничтожение p с хвостом цепочки "at_exit", так что при окончании программы будет выполнено правильное уничтожение.

[EDIT] Поскольку это требование для C ++ 11 и реализовано только в некоторых предварительных стандартах C ++ 03, проверьте реализацию и настройки компилятора.

Прямо сейчас я могу только гарантировать, что MinGW 4.6 и VS2010 уже сделали это.

2 голосов
/ 30 января 2012

Нет.Там нет абсолютно никакой разницы между вашим кодом и двойной проверкой блокировки.Правильная реализация :

static std::mutex m;

Singleton&
Singleton::instance()
{
    static Singleton* theOneAndOnly;
    std::lock_guard l(m);
    if (theOneAndOnly == NULL)
        theOneAndOnly = new Singleton;
    return *theOneAndOnly;
}

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

1 голос
/ 30 января 2012

Этот код содержит условие гонки, в котором created может быть прочитано, когда он одновременно записывается другим потоком.

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

Как отметил Кенни в комментариях, гораздо лучшая альтернатива:

Instance* GetInstance() { static Instance instance; return &instance; }
0 голосов
/ 30 января 2012

ваше решение будет работать нормально, но оно выполнит еще одну проверку.

правая двойная проверка блокировки выглядит,

static Instance *instance = 0; 

Instance *GetInstance() { 
    if (instance == NULL)  //first check.
    {
        Lock lock; //scope lock.
        if (instance == NULL) //second check, the second check must under the lock.
           instance = new Instance;
    }

    return instance;
} 

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

...