Сделайте нить Meyers Singleton безопасной и быстрой с ленивой оценкой - PullRequest
1 голос
/ 03 апреля 2012

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

static Singleton& getInstance()
{
     static Singleton singleton;
     return singleton;
}

PS: и да, я также много читал о поточно-ориентированной реализации Singletom, когда мы используем указатель Singleton в качестве члена класса, вопрос об этой конкретной реализации Singleton, без указателей и новых, и с использованием отложенной оценки.

Ответы [ 3 ]

2 голосов
/ 07 января 2013

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

Если у вас есть дополнительный поток поддержки, вы можете использовать boost::call_once для инициализации.Это потокобезопасно и стоит только при первой инициализации.

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

Все они, даже boost::call_once, применимы только к созданию объекта.Однако для доступа к нему могут потребоваться отдельные методы синхронизации, если к ним обращаются несколько потоков.

(Кстати, пункт 47 Meyers Effective C ++, где упоминается этот синглтон, предполагает, что более поздние пересмотры стандарта сделали его поточно-ориентированным, а более поздние компиляторысогласуется с ним, однако предупреждает, что еще не все компиляторы совместимы).

0 голосов
/ 06 апреля 2012

Так что ответ на мой вопрос лучше всего выразил Фред Ларсон в своем комментарии:

"Быстро, потокобезопасен, ленив - выбирайте любые два."

0 голосов
/ 03 апреля 2012

ОК, так что вы вообще не можете сделать это без мьютекса, но вы можете сделать этот мьютекс быстрым.

Сначала объявите класс для хранения мьютекса и флаг готовности (InitMutex ниже). Когда вызывается GrabMutex() и ready равно false, фактический мьютекс захватывается. Когда вызывается ReleaseMutex(), он делает все правильно, основываясь на флаге, отправленном с GrabMutex(). После первого выполнения готово становится истинным, поскольку статический объект теперь инициализирован, поэтому мьютекс не нужно захватывать.

Теперь объявите класс, который вызывает GrabMutex() в конструкторе и ReleaseMutex(flag) в деструкторе, сохраняя флаг локально (InitMutexHolder ниже).

Теперь создайте экземпляр этого класса в вашей обычной функции getSingleton. Это будет гарантировать, что инициализация синглтона будет мьютексирована с первого раза, и если несколько потоков будут бороться, они выстроятся в мьютекс. Но как только инициализируется синглтон, ready сбывается, и доступ будет быстрым. Деструктор вызывается магически после выполнения return theSingleton, что освобождает мьютекс (или ничего не делает, если мьютекс не был взят).

Но основная часть кода в вашей функции theSingleton() не изменилась, мы добавили только контрольный объект для синглтона и стековый объект в вызове, который управляет безопасностью потока.

Примечание о барьерах записи: поскольку код безопасен всякий раз, когда ready ложно, и ready не может сбываться до тех пор, пока объект не будет инициализирован, код в целом безопасен, если предположить, что записи мгновенно видны. Но может быть задержка в отображении ready=true, так как нет барьера записи после установки true готовности. Однако в течение этой задержки безопасность все еще сохраняется, поскольку ready=false является консервативным, безопасным случаем.

class InitMutex
{
public:
   InitMutex() : ready(false) { }

   bool  GrabMutex()
   {
      if (!ready)
      {
         mutex.Grab();
         return true;
      }
      else
      {
         return false;
      }
   }

   void ReleaseMutex(bool flagFromGrabMutex)
   {
      if (flagFromGrabMutex)
      {
          mutex.Release();
          ready = true;
      }
   }

   Mutex   mutex;
   bool    ready;
};

class InitMutexHolder
{
public:
    InitMutexHolder(InitMutex & m)
    : initMutex(m)
    {
       inMutex = initMutex.GrabMutex();
    }
    ~InitMutexHolder()
    {
       initMutex.ReleaseMutex(inMutex);
    }

private:
    bool inMutex;
    InitMutex & initMutex;
};

static InitMutex singletonMutex;
static Singleton & getSingleton()
{
    InitMutexHolder mutexHolder(singletonMutex);
    {
       static Singleton theSingleton;
       return theSingleton;
    }   
}
...