Re[5]: thread safe singlton - возможно ли такое в принципе
От: Vamp Россия  
Дата: 06.02.04 16:13
Оценка:
Попытка номер 3
static MeyersSingleton &Instance()
    {
        static volatile MeyersSingleton* the_ton=NULL;   
        if (!the_ton) {  
           HANDLE h;
           h=CreateMutex(NULL, true, "mutext");
           static MeyersSingleton obj;
           the_ton=&obj;
           ReleaseMutex(h);
           CloseHandle(h);
        } 
        return *the_ton;
    }


AS>И так медленно И неправильно.

AS>2. объвлять его членом класса.
Зачем?
AS>4. Создание объекта для надежности в отдельную статическую функцию (дабы не искушать оптимизатор).
Нечего ему тут оптимизровать.
Да здравствует мыло душистое и веревка пушистая.
Re: thread safe singlton - возможно ли такое в принципе
От: PM  
Дата: 09.02.04 08:39
Оценка:
Здраствуйте, Andrew S. Вы писали:

AS> Всем доброго времени суток.

AS> Итак, как было выяснено
AS> здесь[/url<br />
<span class='lineQuote level1'> AS&gt; ], синглтон Майерса не является потокобезопасным.</span><br />
<br />
[]<br />
Д. Шмидт рассматривал это в паттерне [url=http://www.cs.wustl.edu/~schmidt/PDF/DC-Locking.pdf]Double-Checked Locking
Автор: Andrew S
Дата: 06.02.04
Posted via RSDN NNTP Server 1.7 "Bedlam"
Re: Мой вариант. Работает ли это в принципе?
От: Аноним  
Дата: 21.06.04 08:02
Оценка:
Самому понадобилось: попробовал сделать сам.

template <class T>
class CSingleton
{
    static T* pInstance;
    static CCriticalSection cs;

protected:
    CSingleton();
    CSingleton(const CSingleton&);
    CSingleton& operator=(const CSingleton&);
    virtual ~CSingleton(){pInstance = NULL;}

public:
    static T* Instance(void);
    void Free(void);
}

template <class T>
T* CSingleton::pInstance = NULL;

template <class T>
T* CSingleton::Instance()
{
    if(!pInstance)
    {
        cs.Lock();

        if(!pInstance)
        {
            pInstance = new T;
        }

        cs.Unlock();
    }
}

template <class T>
void CSingleton::Free()
{
    if(pInstance)
    {
        cs.Lock();

        if(pInstance)
        {
            delete this;
        }
        cs.Unlock();
    }
}
Re[2]: Мой вариант. Работает ли это в принципе?
От: kmn Украина  
Дата: 21.06.04 08:32
Оценка:
Здравствуйте, Аноним, Вы писали:

А>Самому понадобилось: попробовал сделать сам.


А>[ccode]

А>template <class T>
А>class CSingleton
А>{
А> static T* pInstance;
А> static CCriticalSection cs;

А>protected:

А> CSingleton();
А> CSingleton(const CSingleton&);
А> CSingleton& operator=(const CSingleton&);
А> virtual ~CSingleton(){pInstance = NULL;}

А>public:

А> static T* Instance(void);
А> void Free(void);
А>}

Не дороговато ли хранить для каждого сингелтона свой объект синхронизации?
Re[2]: Мой вариант. Работает ли это в принципе?
От: Павел Кузнецов  
Дата: 21.06.04 15:39
Оценка:
>
> template <class T>
> T* CSingleton::Instance()
> {
>     if(!pInstance)
>     {
>         cs.Lock();
>         if(!pInstance)
>             pInstance = new T;
>         cs.Unlock();
>     }
> }
>


Это так называемый Double Checked Locking. В общем случае, без использования специфических для платформы средств для организации memory barrier, не работает. Немного подробнее: http://rsdn.ru/Forum/Message.aspx?mid=380025&amp;only=1
Автор: Павел Кузнецов
Дата: 10.09.03
Posted via RSDN NNTP Server 1.9 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re[3]: Мой вариант. Работает ли это в принципе?
От: Блудов Павел Россия  
Дата: 22.06.04 03:28
Оценка:
Здравствуйте, kmn, Вы писали:

kmn>Не дороговато ли хранить для каждого сингелтона свой объект синхронизации?


Нет. Если singleton "легче" чем Critical Section — для него нет смысла
делать отложенную инициализацию.
... << Rsdn@Home 1.1.4 beta 1 >>
Re[3]: Мой вариант. Работает ли это в принципе?
От: Аноним  
Дата: 22.06.04 07:14
Оценка:
Здравствуйте, Павел Кузнецов, Вы писали:

ПК>Это так называемый Double Checked Locking. В общем случае, без использования специфических для платформы средств для организации memory barrier, не работает. Немного подробнее: http://rsdn.ru/Forum/Message.aspx?mid=380025&amp;only=1
Автор: Павел Кузнецов
Дата: 10.09.03


Как я понял из этой статьи надо сделать как-то так:

template <class T>
T* CSingleton<T>::Instance()
{
    if(!pinstance_)
    {
        cs_.Lock();

        if(!pinstance_)
        {
            cs__.Lock();
            pinstance_ = new T;
            cs__.Unlock();
        }

        cs_.Unlock();
    }

    return pinstance_;
}
Re[4]: Мой вариант. Работает ли это в принципе?
От: WolfHound  
Дата: 22.06.04 09:39
Оценка:
Здравствуйте, <Аноним>, Вы писали:

ПК>>Это так называемый Double Checked Locking. В общем случае, без использования специфических для платформы средств для организации memory barrier, не работает. Немного подробнее: http://rsdn.ru/Forum/Message.aspx?mid=380025&amp;only=1
Автор: Павел Кузнецов
Дата: 10.09.03


Вот Double Checked Locking с memory barrier
Re: thread safe singlton &mdash; возможно ли такое в принципе
Автор: WolfHound
Дата: 06.02.04

Велосипед уже изобретен.
... << RSDN@Home 1.1.3 beta 1 >>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[2]: thread safe singlton - возможно ли такое в принципе
От: bw  
Дата: 22.06.04 10:57
Оценка:
Здравствуйте, WolfHound, Вы писали:

WH>Здравствуйте, Andrew S, Вы писали:


WH>Толком не тестировал но по идеи так

<nice code skepped>

Это же DCLP. Не на всех платформах будет работать. Хотя на IA-32 проблем конечно нет.
Re[3]: thread safe singlton - возможно ли такое в принципе
От: Andrew S Россия http://alchemy-lab.com
Дата: 22.06.04 11:07
Оценка:
bw>Это же DCLP. Не на всех платформах будет работать. Хотя на IA-32 проблем конечно нет.

Это не так. Обратите внимание на atom_get\atom_set
http://www.rusyaz.ru/pr — стараемся писАть по-русски
Re[5]: Мой вариант. Работает ли это в принципе?
От: Yacha Россия  
Дата: 22.06.04 11:17
Оценка:
Здравствуйте, WolfHound, Вы писали:

WH>Здравствуйте, <Аноним>, Вы писали:


ПК>>>Это так называемый Double Checked Locking. В общем случае, без использования специфических для платформы средств для организации memory barrier, не работает. Немного подробнее: http://rsdn.ru/Forum/Message.aspx?mid=380025&amp;only=1
Автор: Павел Кузнецов
Дата: 10.09.03


WH>Вот Double Checked Locking с memory barrier

WH>Re: thread safe singlton &mdash; возможно ли такое в принципе
Автор: WolfHound
Дата: 06.02.04

WH>Велосипед уже изобретен.
А можно немножко прокоментировать код, на предмет, как здесь реализованы memory barrier-ы.
В статье Скота Меерса, говорится о двух типах барьеров(aquire/release). http://www.nwcpp.org/Downloads/2004/DCLP_notes.pdf
Re[4]: thread safe singlton - возможно ли такое в принципе
От: bw  
Дата: 22.06.04 11:22
Оценка:
Здравствуйте, Andrew S, Вы писали:

bw>>Это же DCLP. Не на всех платформах будет работать. Хотя на IA-32 проблем конечно нет.


AS>Это не так. Обратите внимание на atom_get\atom_set


Видел. Их обоснованность вызвает у меня большие сомнения.
Interlocked функции гарантируют атомарность конкретной операции над памятью, но не имеют никакого отношения к переупорядочиванию доступа к памяти. Именно в этом проблема DCLP. Например, мы можем получить в obj_ptr адрес недоконстурированного объекта.

static T obj; //write 1: obj = <initialization value>
ptr=&obj;
atom_set((LONG*)&obj_ptr, (LONG)ptr); //write 2: obj_ptr = obj

Эти операции записи могут быть переупорядочены и другой процессор увидит ненулевое значение в obj_ptr еще до того, как произойдет инициализация obj.
Re[5]: thread safe singlton - возможно ли такое в принципе
От: Andrew S Россия http://alchemy-lab.com
Дата: 22.06.04 12:06
Оценка:
bw>Видел. Их обоснованность вызвает у меня большие сомнения.
bw>Interlocked функции гарантируют атомарность конкретной операции над памятью, но не имеют никакого отношения к переупорядочиванию доступа к памяти. Именно в этом проблема DCLP. Например, мы можем получить в obj_ptr адрес недоконстурированного объекта.

bw>static T obj; //write 1: obj = <initialization value>

bw>ptr=&obj;
bw>atom_set((LONG*)&obj_ptr, (LONG)ptr); //write 2: obj_ptr = obj

bw>Эти операции записи могут быть переупорядочены и другой процессор увидит ненулевое значение в obj_ptr еще до того, как произойдет инициализация obj.


На мой взгляд — это уже проблема самого объекта и компилятора. Если предполагается, что он будет использоваться таким образом (т.е. в MP среде), то вполне очевидно, что компилятор должен обеспечить валидность объекта (т.е. его полное конструирование) до первого обращения к объекту (либо взятия его адреса\ссылки). Иначе любая, даже однопоточная программа, будет постоянно иметь дело с необходимостью "ручной" синхронизации кешей процессоров.
http://www.rusyaz.ru/pr — стараемся писАть по-русски
Re[6]: thread safe singlton - возможно ли такое в принципе
От: bw  
Дата: 22.06.04 12:56
Оценка:
Здравствуйте, Andrew S, Вы писали:


bw>>static T obj; //write 1: obj = <initialization value>

bw>>ptr=&obj;
bw>>atom_set((LONG*)&obj_ptr, (LONG)ptr); //write 2: obj_ptr = obj

bw>>Эти операции записи могут быть переупорядочены и другой процессор увидит ненулевое значение в obj_ptr еще до того, как произойдет инициализация obj.


AS>На мой взгляд — это уже проблема самого объекта и компилятора. Если предполагается, что он будет использоваться таким образом (т.е. в MP среде), то вполне очевидно, что компилятор должен обеспечить валидность объекта (т.е. его полное конструирование) до первого обращения к объекту (либо взятия его адреса\ссылки). Иначе любая, даже однопоточная программа, будет постоянно иметь дело с необходимостью "ручной" синхронизации кешей процессоров.


Инициализация объекта для процессора по сути такая же операция записи, как и масса других. Если компилятор между всеми операциями записи будет вставлять барьер, то производительность загнется.

Я думаю, тебя сбил с тольку термин "переупорядочивиание". Поясню: на одном процессоре мы выполняем приведенный выше код. Если этот процессор призведет write reoder, то другой процессор, выполняющий другой поток нашего процесса, может увидеть сначала результат второй операции, а потом результат первой. А вот наш первый процессор всегда будет видеть операции записи в правильном порядке, для него видимое поведение не изменится, будет ли write reoder или нет.

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

PPS. Насчtт DCLP можно почитать вот это http://www.nwcpp.org/Downloads/2004/DCLP_notes.pdf
Только не дай Мейерсу ввести себя в заблуждения синхронизацией кешей . Мейерс ошибается. Нет такой проблемы.
Re: thread safe singlton - возможно ли такое в принципе
От: VVB16 Россия  
Дата: 22.06.04 13:09
Оценка:
Здравствуйте, Andrew S, Вы писали:

Часть реализации из ACE (по комментариям понятно, что используется):

template <class TYPE, class ACE_LOCK> ACE_Singleton<TYPE, ACE_LOCK> *&
ACE_Singleton<TYPE, ACE_LOCK>::instance_i (void)
{
#if defined (ACE_LACKS_STATIC_DATA_MEMBER_TEMPLATES)
  // Pointer to the Singleton instance.  This works around a bug with
  // G++ and it's (mis-)handling of templates and statics...
  static ACE_Singleton<TYPE, ACE_LOCK> *singleton_ = 0;

  return singleton_;
#else
  return ACE_Singleton<TYPE, ACE_LOCK>::singleton_;
#endif /* ACE_LACKS_STATIC_DATA_MEMBER_TEMPLATES */
}

template <class TYPE, class ACE_LOCK> TYPE *
ACE_Singleton<TYPE, ACE_LOCK>::instance (void)
{
  ACE_TRACE ("ACE_Singleton<TYPE, ACE_LOCK>::instance");

  ACE_Singleton<TYPE, ACE_LOCK> *&singleton =
    ACE_Singleton<TYPE, ACE_LOCK>::instance_i ();

  // Perform the Double-Check pattern...
  if (singleton == 0)
    {
      if (ACE_Object_Manager::starting_up () ||
          ACE_Object_Manager::shutting_down ())
        {
          // The program is still starting up, and therefore assumed
          // to be single threaded.  There's no need to double-check.
          // Or, the ACE_Object_Manager instance has been destroyed,
          // so the preallocated lock is not available.  Either way,
          // don't register for destruction with the
          // ACE_Object_Manager:  we'll have to leak this instance.

          ACE_NEW_RETURN (singleton, (ACE_Singleton<TYPE, ACE_LOCK>), 0);
        }
      else
        {
#if defined (ACE_MT_SAFE) && (ACE_MT_SAFE != 0)
          // Obtain a lock from the ACE_Object_Manager.  The pointer
          // is static, so we only obtain one per ACE_Singleton
          // instantiation.
          static ACE_LOCK *lock = 0;
          if (ACE_Object_Manager::get_singleton_lock (lock) != 0)
            // Failed to acquire the lock!
            return 0;

          ACE_GUARD_RETURN (ACE_LOCK, ace_mon, *lock, 0);

          if (singleton == 0)
            {
#endif /* ACE_MT_SAFE */
              ACE_NEW_RETURN (singleton, (ACE_Singleton<TYPE, ACE_LOCK>), 0);

              // Register for destruction with ACE_Object_Manager.
              ACE_Object_Manager::at_exit (singleton);
#if defined (ACE_MT_SAFE) && (ACE_MT_SAFE != 0)
            }
#endif /* ACE_MT_SAFE */
        }
    }

  return &singleton->instance_;
}


Причем Lock тут может быть любой — thread_mutex (CriticalSection в Win) и т.п.
Подробнее — в самом ACE.

--
Vitaly Belekhov
Re[6]: thread safe singlton - возможно ли такое в принципе
От: Павел Кузнецов  
Дата: 22.06.04 13:43
Оценка:
> bw>Interlocked функции гарантируют атомарность конкретной операции над памятью, но не имеют никакого отношения к переупорядочиванию доступа к памяти.

В соответствии с MSDN эти функции включают memory barrier на тех системах, где это нужно.

> Именно в этом проблема DCLP. Например, мы можем получить в obj_ptr адрес недоконстурированного объекта.

>
> bw>static T obj; //write 1: obj = <initialization value>
> bw>ptr=&obj;
> bw>atom_set((LONG*)&obj_ptr, (LONG)ptr); //write 2: obj_ptr = obj
>
> bw>Эти операции записи могут быть переупорядочены и другой процессор увидит ненулевое значение в obj_ptr еще до того, как произойдет инициализация obj.
>
> На мой взгляд — это уже проблема самого объекта и компилятора. Если предполагается, что он будет использоваться таким образом (т.е. в MP среде), то вполне очевидно, что компилятор должен обеспечить валидность объекта (т.е. его полное конструирование) до первого обращения к объекту (либо взятия его адреса\ссылки).

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

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


Это не так: если запись переупорядочивает компилятор, то однопоточная программа будет получать значения не из памяти, а из регистров. Если же запись переупорядочивает процессор, то поток, выполняющийся на данном процессоре, будет получать значения из его кэша. Проблемы начнутся только при доступе к объекту из другого потока без внешней синхронизации.

Т.к. в данном случае memory barrier присутствует (в виде Interlocked*), все должно быть в порядке.
Posted via RSDN NNTP Server 1.9 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re[7]: thread safe singlton - возможно ли такое в принципе
От: Andrew S Россия http://alchemy-lab.com
Дата: 22.06.04 14:56
Оценка:
ПК>Это не так: если запись переупорядочивает компилятор, то однопоточная программа будет получать значения не из памяти, а из регистров. Если же запись переупорядочивает процессор, то поток, выполняющийся на данном процессоре, будет получать значения из его кэша. Проблемы начнутся только при доступе к объекту из другого потока без внешней синхронизации.

ПК>Т.к. в данном случае memory barrier присутствует (в виде Interlocked*), все должно быть в порядке.


Да, но он присутствует относительное не самого объекта, а указателя на него. Если atom_get\atom_set гарантируют, что процессоры будут синхронизированы относительно общего состояния (т.е. синхронизация очередей команд, кеша и прочее) — тогда конечно. (насколько я помню, interlocked это гарантирует только для собственно указателя, точнее, для той области памяти, что представляет значение указателя. На самом деле он гарантирует, что, изменяя значение, мы корректно его изменим + получим обратно то, что было. Кстати, реализация atom_get ака WolfHound абсолюно бессмысленна — подробнее в Рихтере).
Т.о. ситуация, описанная WB (т.е. когда мы осуществили атомарно изменение указателя, но не уведомили другие процессоры об инициализации T) вполне возможна.

Вот из описания команды\сигнала lock

Команда с захватом гарантированно захватывает только область памяти, определяемую операндом назначения, но может захватить и большую область памяти. Например, типовые конфигурации 8086 и 80286 захватывают все физическое адресное пространство.

http://www.rusyaz.ru/pr — стараемся писАть по-русски
Re[8]: thread safe singlton - возможно ли такое в принципе
От: Andrew S Россия http://alchemy-lab.com
Дата: 22.06.04 15:32
Оценка:
AS>Вот из описания команды\сигнала lock

AS>

AS>Команда с захватом гарантированно захватывает только область памяти, определяемую операндом назначения, но может захватить и большую область памяти. Например, типовые конфигурации 8086 и 80286 захватывают все физическое адресное пространство.


Хотя, с другой стороны, из манула IA32:

Locked operations are atomic with respect to all other memory operations and
all externally visible events. Only instruction fetch and page table
accesses can pass locked instructions. Locked instructions can be used to
synchronize data written by one processor and read by another processor.

For the P6 family processors, locked operations serialize all outstanding
load and store operations (that is, wait for them to complete).
This rule is
also true for the Pentium 4 and Intel Xeon processors, with one exception:
load operations that reference weakly ordered memory types (such as the WC
memory type) may not be serialized.


Из этого куска следует, что lock для семейства P6 наконец-то начинает работать как memory barrier (и в этом случае atom_get ака WolfHound будет работать). Великолепно, но как быть с P5 и 486-386?
http://www.rusyaz.ru/pr — стараемся писАть по-русски
Re[9]: thread safe singlton - возможно ли такое в принципе
От: Павел Кузнецов  
Дата: 22.06.04 16:38
Оценка: 10 (2)
>

> For the P6 family processors, locked operations serialize all outstanding load and store operations (that is, wait for them to complete). This rule is also true for the Pentium 4 and Intel Xeon processors, with one exception: load operations that reference weakly ordered memory types (such as the WC memory type) may not be serialized.

>
> Из этого куска следует, что lock для семейства P6 наконец-то начинает работать как memory barrier

Да, именно это я и имел в виду.

> но как быть с P5 и 486-386?


Эти процессоры не переупорядочивают запись, так что им memory barrier не нужен, достаточно атомарности изменений.
Posted via RSDN NNTP Server 1.9 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re[7]: thread safe singlton - возможно ли такое в принципе
От: bw  
Дата: 22.06.04 17:11
Оценка:
Здравствуйте, Павел Кузнецов, Вы писали:

>> bw>Interlocked функции гарантируют атомарность конкретной операции над памятью, но не имеют никакого отношения к переупорядочиванию доступа к памяти.


ПК>В соответствии с MSDN эти функции включают memory barrier на тех системах, где это нужно.


<skipped>

ПК>Т.к. в данном случае memory barrier присутствует (в виде Interlocked*), все должно быть в порядке.


Действительно, InterlockedExchange генерирует нужный mb — мой MSDN оказался староват
Зачем это понадобилось MS не знаю. Наверное, чтобы старый плохой код продолжал работать...

Однако, даже если отвлечься от того, что решение получилось WIN API-specific, хотя автор наверняка имел в виду общее решение, то и в этом случае решение нерабочее.

Неправильно в нем то, что чтение тоже может быть переупорядочено.


    // код WoulfHound
    static T &Instance()
    {
        static T* obj_ptr=0;//Статическая инициализация(потокобезопасно)
        static LONG flag=0;//Аналогично
        T* ptr=(T*)atom_get((LONG*)&obj_ptr); //read 1
        if(!ptr)
        {
            auto_lock lock(&flag);
            ptr=(T*)atom_get((LONG*)&obj_ptr);
            if(!ptr)
            {
                static T obj;//Создастся пи первом поподании сюда
                ptr=&obj;
                atom_set((LONG*)&obj_ptr, (LONG)ptr);
            }
        }
        return *ptr;
    }
    
    //код предполагаемого пользователя синглтона
    some_call(); //read 0
    T x = Instance();
    std::cout << x; //read 2



В точке read_1 мы читаем значение указателя, в точке read_2 мы читаем данные по этому указателю.
Эти две операции могут быть переупорядочены. Может возникнуть вопрос, как такое может случиться, если мы сначала читаем указатель, по которому затем уже читаем данные — может. Если каким-то образом предшествующий использованию синглтона some_call() произвел чтение read_0 из будущего адреса obj_ptr.

На IA-64 такое невозможно — там есть соответствующий data-dependency restriction.
На альфе возможно: http://www.cs.umd.edu/~pugh/java/memoryModel/AlphaReordering.html
На SparcV9 кажется тоже возможно. Буду благодарен, если кто-нибудь сможет это подтвердить или опровергнуть.

Чтобы проблемы не было надо ставить acquire lock после atom_get, а InterlockedExchangeAdd, через который реализован atom_get этого не делает.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.