> 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[4]: thread safe singlton - возможно ли такое в принципе
И так медленно И неправильно.
1. static volatile bool
2. объвлять его членом класса.
3. Не скомпилируется — obj out of scope.
4. Создание объекта для надежности в отдельную статическую функцию (дабы не искушать оптимизатор).
5. Двойная проверка некорректно работает на некоторых SMP системах. Вообще ее не рекомендуют использовать — то там баг отловится, то там...
В общем, работающее решение предложил WolfHound, но мне оно по ряду причин не нравится и рекомендовать его к использованию я не могу. Но оно рабочее и даже в некотором смысле кошерное.
Processors can be instructed to force their memory caches to agree with main memory with special instructions. Such instructions ensure that previous read and write requests have completed and are made visible to other processors, and to ensure that that no subsequent read or write requests have started. Examples are:
Functions which enter or leave critical sections.
Functions which signal synchronization objects.
Wait functions. Interlocked functions
<...>
The InterlockedExchange function ensures that the value of iValue is updated for all processors before the value of fValueHasBeenComputed is set to TRUE.
Это и есть неформальное описание memory barrier.
Posted via RSDN NNTP Server 1.9 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
_>>Спросил только для того что бы получше понять смысл написанного. Человек я простой, только простые вещи понимаю. Насколько я понимаю, все будет работать даже так: WH>хъ _>>Правильно? WH>А вот тут нет memory barrier(он встроен в InterlockedXXX функции). Так что теоритически могут быть проблемы.
Имхо, просто этот код тормознее, поскольку возможны конфликты, и, соотв, попадание в sleep при одновременном входе из разных потоков... Двойная проверка как раз делается для того, чтобы быстрее было. А выше был приведен классический "медленный" вариант. Любоай auto_lock, предоставляемый средствами OS, должен являться memmory barrier.
Здравствуйте, Andrew S, Вы писали:
L>>Это как раз совершенно не серьезное преимущество. L>>В общем, я могу предложить вариант, который наверное устроит и тебя и меня. L>>Сделать синглетон-критическую секцию, создание которой защитить lightweight_mutex'ом (создание тут быстрое, так что можно). И вот эту критическую секцию и использовать при инициализации остальных синглетонов.
AS>Одну критическую секция на все? Несерьезно, тормоза будут дикие, более того может
Совсем не обязательно. Можно, например, сделать шаблонную "фабрику" критических секций.
AS> быть так, что создание одного синглтона зависит от другого, и это происходит в разных потоках. Придется все-равно по одному именованному объекту на синглтон. Мне кажется, это не очень хорошая мысль. А точнее, очень _не_ хорошая. Впрочем, каждому
А где ты в критической секции увидел именованные объекты?
AS> свое AS>В любом случае, серебряной пули не существует...
Это верно.
... << RSDN@Home 1.1.4 beta 1 >>
"Будь достоин победы" (c) 8th Wizard's rule.
thread safe singlton - возможно ли такое в принципе
Первое, что я подумал — сделать синглтон на хипе, создание защитить критическими секциями. Однако, критические секции (ну или мутексы, какая разница...) перед использованием надо инициализировать. Но, поскольку для синхронизации нам надо уже иметь проинициализированной критическую секкцию, опять приходим к идиоме синглтона майерса (уже для инициализации критической секции) со всем вытекающими проблемами потоконебезопасности.
Какие варианты подскажет уважаемая общественность?
AS>Какие варианты подскажет уважаемая общественность?
Не совсем понятно, почему студия должна генерить потоко-независимый код. Если бы она это делала, то какая была бы эффективность этого кода? Об этом должен заботиться разработчик.
Re[2]: thread safe singlton - возможно ли такое в принципе
AS>>Какие варианты подскажет уважаемая общественность? ВН>Не совсем понятно, почему студия должна генерить потоко-независимый код. Если бы она это делала, то какая была бы эффективность этого кода? Об этом должен заботиться разработчик.
Да, забыл сказать, мьютекс можно создать без проблем, т. к. если два потока одновременно попытаются создать мьютексы с одинаковым именем, один из них получит отказ, и должен будет проверить, а не создан ли мьютекс пока он его создавал. Если да, то его просто нужно открыть.
Re[2]: thread safe singlton - возможно ли такое в принципе
, синглтон Майерса не является потокобезопасным.
AD>У Александреску есть хороший потокобезопасный синглтон.
Нет у него _работающего_ потокобезопасного синглетона, к сожалению. То, что есть — основывается на том, что критическая секция инициализируется в конструкторе синглетона. А тот, в свою очередь, статически создается в cpp файле. Тогда уже можно и целевой объект статически создавать в cpp файле, напарываясь при этом на неопределенность инициализации статических объектов в разных модулях. За что боролись, на то и напоролись.
ВН>>Не совсем понятно, почему студия должна генерить потоко-независимый код. Если бы она это делала, то какая была бы эффективность этого кода? Об этом должен заботиться разработчик.
Ну, этот код вызывается один раз при первом вызове функции — эффективность не сильно пострадала бы. А вот нервы разработчика — точно были бы целее.
ВН>Да, забыл сказать, мьютекс можно создать без проблем, т. к. если два потока одновременно попытаются создать мьютексы с одинаковым именем, один из них получит отказ, и должен будет проверить, а не создан ли мьютекс пока он его создавал. Если да, то его просто нужно открыть.
Верно, но это только для именованых мьютексов. Представляете, какая производительность такого кода будет?
Честно говоря, я так и думал, что будут советовать нечто в этом роде
Я сам рассматривал возможность синхронизации через семейство Interlockedxxx функций, но, согласитесь, выглядит это по меньшей мере страшненько (на самом деле я бы такой код в проекте не допустил, уж лучше инициализировать синглетон статически в файле реализации и потерять безопасность на этапе конструирования статических объектов — т.е. гарантировать, что на этом этапе вызова Instance не будет. Это проще и безопаснее, чем написать такое решение...). Но за попытку спасибо!
Здравствуйте, Andrew S, Вы писали:
AS>Я сам рассматривал возможность синхронизации через семейство Interlockedxxx функций, но, согласитесь, выглядит это по меньшей мере страшненько (на самом деле я бы такой код в проекте не допустил, уж лучше инициализировать синглетон статически в файле реализации и потерять безопасность на этапе конструирования статических объектов — т.е. гарантировать, что на этом этапе вызова Instance не будет. Это проще и безопаснее, чем написать такое решение...). Но за попытку спасибо!
Чем лучше? Я эту идею из boost позаимствовал...
... << RSDN@Home 1.1.3 beta 1 >>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re: thread safe singlton - возможно ли такое в принципе
Здравствуйте, Andrew, Вы писали:
AS> Первое, что я подумал — сделать синглтон на хипе, создание AS> защитить критическими секциями.
Не обязательно создавать объект singleton в динамической памяти. Достаточно
предварить определение статического объекта захватом мьютекса или критической
секции.
AS> Однако, критические секции (ну или мутексы, какая разница...) перед AS> использованием надо инициализировать.
Один из вариантов — инициализировать критическую секцию/мьютекс заведомо до
использования singleton.
Если структура программы настолько аморфна, что ничего о времени использования
тех или иных объектов сказать нельзя, возможно, стоит задуматься об архитектуре.
Posted via RSDN NNTP Server 1.7 "Bedlam"
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re[4]: thread safe singlton - возможно ли такое в принципе
WH>Я эту идею из boost позаимствовал...
если мне память не изменяет, то из lightweight_mutex... только я бы Sleep(0) оставил — так квант отдаеться, а в твоем случае задержка, хоть и маленькая.
Невозможное мы сделаем сегодня — чудо займет немного больше времени. /Аноним/
Re[2]: thread safe singlton - возможно ли такое в принципе
ПК>Один из вариантов — инициализировать критическую секцию/мьютекс заведомо до ПК>использования singleton.
Верно. Например, в файле реализации синглетона, как это сделано у Александреску.
Но тогда возможна ситуация вызова Instance с еще непроинициализированной критической секцией. В общем, за что боролись... Печально, Павел, неужели нет красивого выхода из этой ситауции?
ПК>Если структура программы настолько аморфна, что ничего о времени использования ПК>тех или иных объектов сказать нельзя, возможно, стоит задуматься об архитектуре.
Нет, меня вполне устраивает объявление объекта синглтона в файле реализации и я могу гарантировать, что Instance будет вызываться только после инициализации статических объектов. Но меня интересует не конкретный проект, а именно принцип.
AS>>Я сам рассматривал возможность синхронизации через семейство Interlockedxxx функций, но, согласитесь, выглядит это по меньшей мере страшненько (на самом деле я бы такой код в проекте не допустил, уж лучше инициализировать синглетон статически в файле реализации и потерять безопасность на этапе конструирования статических объектов — т.е. гарантировать, что на этом этапе вызова Instance не будет. Это проще и безопаснее, чем написать такое решение...). Но за попытку спасибо! WH>Чем лучше? Я эту идею из boost позаимствовал...
Ну, мы люди темные, бустом не пользуемся Но такой вариант я придумал сразу и отверг его, содрогнувшись в душе.
Как говорил Туполев — некрасивый самолет не полетит.
WH>>Я эту идею из boost позаимствовал... U>если мне память не изменяет, то из lightweight_mutex... только я бы Sleep(0) оставил — так квант отдаеться, а в твоем случае задержка, хоть и маленькая.
Нет, это ошибка в MSDN. Квант то отдается, а вот загрузка процесса — становится максимальной.
Здравствуйте, Andrew, Вы писали:
AS> Нет, меня вполне устраивает объявление объекта синглтона в AS> файле реализации и я могу гарантировать, что Instance AS> будет вызываться только после инициализации статических объектов. AS> Но меня интересует не конкретный проект, а именно принцип.
Боюсь, что более-менее общим принципом будет как раз архитектура, позволяющая
отделить инициализацию вспомогательных ресурсов (в данном случае критическая
секция) от инициализации и использования основных объектов (синглтон)... Думаю,
что "локального" платформенно-независимого решения, прозрачного для использующей
программы, нет.
Posted via RSDN NNTP Server 1.7 "Bedlam"
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
Re[2]: thread safe singlton - возможно ли такое в принципе
AS>Мысль правильная, реализация — нет. AS>1. Хэндл не присваивается и не закрывается
Ну, это понятно. Замнем для ясности. AS>2. Медленно.
А если вот так?
Просто читая volatile переменную типа int или long на win32 мы гарантированно получаем валидное значение (старое или новое — вопрос другой, но валидное). Т.о. приведенный код ровным счетом ничего не делает. Так утверждают и господин Рихтер, да и многие другие, и я склонен с ними соглашаться.
AS>И так медленно И неправильно. AS>2. объвлять его членом класса.
Зачем? AS>4. Создание объекта для надежности в отдельную статическую функцию (дабы не искушать оптимизатор).
Нечего ему тут оптимизровать.
Да здравствует мыло душистое и веревка пушистая.
Re: thread safe singlton - возможно ли такое в принципе
Это так называемый Double Checked Locking. В общем случае, без использования специфических для платформы средств для организации memory barrier, не работает. Немного подробнее: http://rsdn.ru/Forum/Message.aspx?mid=380025&only=1
Здравствуйте, 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&only=1
Здравствуйте, <Аноним>, Вы писали:
ПК>>Это так называемый Double Checked Locking. В общем случае, без использования специфических для платформы средств для организации memory barrier, не работает. Немного подробнее: http://rsdn.ru/Forum/Message.aspx?mid=380025&only=1
Здравствуйте, WolfHound, Вы писали:
WH>Здравствуйте, <Аноним>, Вы писали:
ПК>>>Это так называемый Double Checked Locking. В общем случае, без использования специфических для платформы средств для организации memory barrier, не работает. Немного подробнее: http://rsdn.ru/Forum/Message.aspx?mid=380025&only=1
WH>Велосипед уже изобретен.
А можно немножко прокоментировать код, на предмет, как здесь реализованы memory barrier-ы.
В статье Скота Меерса, говорится о двух типах барьеров(aquire/release). http://www.nwcpp.org/Downloads/2004/DCLP_notes.pdf
Re[4]: thread safe singlton - возможно ли такое в принципе
Здравствуйте, Andrew S, Вы писали:
bw>>Это же DCLP. Не на всех платформах будет работать. Хотя на IA-32 проблем конечно нет.
AS>Это не так. Обратите внимание на atom_get\atom_set
Видел. Их обоснованность вызвает у меня большие сомнения.
Interlocked функции гарантируют атомарность конкретной операции над памятью, но не имеют никакого отношения к переупорядочиванию доступа к памяти. Именно в этом проблема DCLP. Например, мы можем получить в obj_ptr адрес недоконстурированного объекта.
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 среде), то вполне очевидно, что компилятор должен обеспечить валидность объекта (т.е. его полное конструирование) до первого обращения к объекту (либо взятия его адреса\ссылки). Иначе любая, даже однопоточная программа, будет постоянно иметь дело с необходимостью "ручной" синхронизации кешей процессоров.
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. И насчет синхронизации кешей — это никогда не является проблемой программиста — это забота проектировщика железа. Как правильно заметили в параллельной ветке — "будь там хоть три кеша".
Можно конечно для упрощения понимать проблему переупорядочивания, как проблему синхронизации кешей, но самом деле это не так.
Часть реализации из 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 - возможно ли такое в принципе
> 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 - возможно ли такое в принципе
ПК>Это не так: если запись переупорядочивает компилятор, то однопоточная программа будет получать значения не из памяти, а из регистров. Если же запись переупорядочивает процессор, то поток, выполняющийся на данном процессоре, будет получать значения из его кэша. Проблемы начнутся только при доступе к объекту из другого потока без внешней синхронизации.
ПК>Т.к. в данном случае memory barrier присутствует (в виде Interlocked*), все должно быть в порядке.
Да, но он присутствует относительное не самого объекта, а указателя на него. Если atom_get\atom_set гарантируют, что процессоры будут синхронизированы относительно общего состояния (т.е. синхронизация очередей команд, кеша и прочее) — тогда конечно. (насколько я помню, interlocked это гарантирует только для собственно указателя, точнее, для той области памяти, что представляет значение указателя. На самом деле он гарантирует, что, изменяя значение, мы корректно его изменим + получим обратно то, что было. Кстати, реализация atom_get ака WolfHound абсолюно бессмысленна — подробнее в Рихтере).
Т.о. ситуация, описанная WB (т.е. когда мы осуществили атомарно изменение указателя, но не уведомили другие процессоры об инициализации T) вполне возможна.
Вот из описания команды\сигнала lock
Команда с захватом гарантированно захватывает только область памяти, определяемую операндом назначения, но может захватить и большую область памяти. Например, типовые конфигурации 8086 и 80286 захватывают все физическое адресное пространство.
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?
Здравствуйте, Павел Кузнецов, Вы писали:
>> bw>Interlocked функции гарантируют атомарность конкретной операции над памятью, но не имеют никакого отношения к переупорядочиванию доступа к памяти.
ПК>В соответствии с MSDN эти функции включают memory barrier на тех системах, где это нужно.
<skipped>
ПК>Т.к. в данном случае memory barrier присутствует (в виде Interlocked*), все должно быть в порядке.
Действительно, InterlockedExchange генерирует нужный mb — мой MSDN оказался староват
Зачем это понадобилось MS не знаю. Наверное, чтобы старый плохой код продолжал работать...
Однако, даже если отвлечься от того, что решение получилось WIN API-specific, хотя автор наверняка имел в виду общее решение, то и в этом случае решение нерабочее.
Неправильно в нем то, что чтение тоже может быть переупорядочено.
// код WoulfHoundstatic T &Instance()
{
static T* obj_ptr=0;//Статическая инициализация(потокобезопасно)static LONG flag=0;//Аналогично
T* ptr=(T*)atom_get((LONG*)&obj_ptr); //read 1if(!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 этого не делает.
Re[10]: thread safe singlton - возможно ли такое в принципе
>> но как быть с P5 и 486-386?
ПК>Эти процессоры не переупорядочивают запись, так что им memory barrier не нужен, достаточно атомарности изменений.
Верно, как то об этом не подумал. Тогда все ок, хотя фраза о том, что майкрософт гарантирует, что InterlockedXxxx для НЕ IA32 архитектур является memory barrier, все-таки настораживает...
> чтение тоже может быть переупорядочено. <...> Чтобы проблемы не было надо ставить acquire lock после atom_get, а InterlockedExchangeAdd, через который реализован atom_get этого не делает.
Если я правильно понял MSDN, то должен делать там, где это нужно...
Posted via RSDN NNTP Server 1.9 beta
Легче одурачить людей, чем убедить их в том, что они одурачены. — Марк Твен
> ПК>Это так называемый Double Checked Locking. В общем случае, без использования специфических для платформы средств для организации memory barrier, не работает. Немного подробнее: http://rsdn.ru/Forum/Message.aspx?mid=380025&only=1
Здравствуйте, Павел Кузнецов, Вы писали:
>> чтение тоже может быть переупорядочено. <...> Чтобы проблемы не было надо ставить acquire lock после atom_get, а InterlockedExchangeAdd, через который реализован atom_get этого не делает.
ПК>Если я правильно понял MSDN, то должен делать там, где это нужно...
Здравствуйте, Andrew S, Вы писали:
ПК>>Один из вариантов — инициализировать критическую секцию/мьютекс заведомо до ПК>>использования singleton.
AS>Верно. Например, в файле реализации синглетона, как это сделано у Александреску. AS>Но тогда возможна ситуация вызова Instance с еще непроинициализированной критической секцией. В общем, за что боролись... Печально, Павел, неужели нет красивого выхода из этой ситауции?
ПК>>Если структура программы настолько аморфна, что ничего о времени использования ПК>>тех или иных объектов сказать нельзя, возможно, стоит задуматься об архитектуре. AS>Нет, меня вполне устраивает объявление объекта синглтона в файле реализации и я могу гарантировать, что Instance будет вызываться только после инициализации статических объектов. Но меня интересует не конкретный проект, а именно принцип.
Не могу понять. Если вы можете гарантировать, что Instance вызывается после входа в main, то какие проблемы с инициализацией критической секции? Никаких.
Если же у вас Instance вызывется до входа в main, то это значит, что нет отложенной инициализации и нет проблем с многопоточностью -- и синглетон вам не нужен тогда, а нужно решить проблему порядка конструирования статических объектов -- но это уже совсем другая проблема.
Здравствуйте, Andrew S, Вы писали:
ПК>>Один из вариантов — инициализировать критическую секцию/мьютекс заведомо до ПК>>использования singleton.
AS>Верно. Например, в файле реализации синглетона, как это сделано у Александреску. AS>Но тогда возможна ситуация вызова Instance с еще непроинициализированной критической секцией. В общем, за что боролись... Печально, Павел, неужели нет красивого выхода из этой ситауции?
Хотите хинт насчет критической секции? Есть критические секции с автоинициализацией. Т.е. для них не нужно вызывать конструктор -- достаточно статической инициализации членов структуры начальными значениями.
ПК>>>Один из вариантов — инициализировать критическую секцию/мьютекс заведомо до ПК>>>использования singleton.
AS>>Верно. Например, в файле реализации синглетона, как это сделано у Александреску. AS>>Но тогда возможна ситуация вызова Instance с еще непроинициализированной критической секцией. В общем, за что боролись... Печально, Павел, неужели нет красивого выхода из этой ситауции?
Ш>Хотите хинт насчет критической секции? Есть критические секции с автоинициализацией. Т.е. для них не нужно вызывать конструктор -- достаточно статической инициализации членов структуры начальными значениями.
Какой конструктор? Вы про что? Я про win32 критические секции
А вообще — теме уже 300 лет, проблема (точнее, вопрос) давно решена...
ПК>>>Если структура программы настолько аморфна, что ничего о времени использования ПК>>>тех или иных объектов сказать нельзя, возможно, стоит задуматься об архитектуре. AS>>Нет, меня вполне устраивает объявление объекта синглтона в файле реализации и я могу гарантировать, что Instance будет вызываться только после инициализации статических объектов. Но меня интересует не конкретный проект, а именно принцип.
Ш>Не могу понять. Если вы можете гарантировать, что Instance вызывается после входа в main, то какие проблемы с инициализацией критической секции? Никаких. Ш>Если же у вас Instance вызывется до входа в main, то это значит, что нет отложенной инициализации и нет проблем с многопоточностью -- и синглетон вам не нужен тогда, а нужно решить проблему порядка конструирования статических объектов -- но это уже совсем другая проблема.
Вы не то выделяете
AS>> Но меня интересует не конкретный проект, а именно принцип.
Своей критикой предложенного синглтона, я просто хотел подчеркнуть, что сами по себе atomic-операции,
имеющиеся практически на любом процессоре, ничего не гарантируют. Вдруг кто захочет спортировать
этот синглетон не под Win — чтобы знали подводные камни
Re: thread safe singlton - возможно ли такое в принципе
Здравствуйте, sergey_shandar, Вы писали:
_>Можно ли этот код сократить до такого?
хъ _>Прежде всего, вопрос не о скорости, а о безопасности.
Вроде можно. А нужно?
... << RSDN@Home 1.1.3 beta 1 >>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[2]: thread safe singlton - возможно ли такое в принципе
Тут недавно в boost.develop появилась забавная нитка, в которой один перец довольно убедительно утверждает, что спин-локи, вообще говоря, довольно хреновая вещь, если у них нет fallback'а на синхронизационные примитивы ОС (там шла дискуссия по поводу boost::lightweight_mutex).
Здравствуйте, WolfHound, Вы писали:
_>>Прежде всего, вопрос не о скорости, а о безопасности. WH>Вроде можно. А нужно?
Спросил только для того что бы получше понять смысл написанного. Человек я простой, только простые вещи понимаю. Насколько я понимаю, все будет работать даже так:
...
WH> static T &Instance()
WH> {
WH> static LONG flag=0;
WH> auto_lock lock(&flag);
WH> static T obj;
WH> return obj;
WH> }
...
Здравствуйте, sergey_shandar, Вы писали:
_>Спросил только для того что бы получше понять смысл написанного. Человек я простой, только простые вещи понимаю. Насколько я понимаю, все будет работать даже так:
хъ _>Правильно?
А вот тут нет memory barrier(он встроен в InterlockedXXX функции). Так что теоритически могут быть проблемы.
... << RSDN@Home 1.1.3 beta 1 >>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[3]: thread safe singlton - возможно ли такое в принципе
Здравствуйте, Lexey, Вы писали:
L>Тут недавно в boost.develop появилась забавная нитка, в которой один перец довольно убедительно утверждает, что спин-локи, вообще говоря, довольно хреновая вещь, если у них нет fallback'а на синхронизационные примитивы ОС (там шла дискуссия по поводу boost::lightweight_mutex).
А можно ее сюда процитировать?
... << RSDN@Home 1.1.3 beta 1 >>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[4]: thread safe singlton - возможно ли такое в принципе
Здравствуйте, WolfHound, Вы писали:
L>>Тут недавно в boost.develop появилась забавная нитка, в которой один перец довольно убедительно утверждает, что спин-локи, вообще говоря, довольно хреновая вещь, если у них нет fallback'а на синхронизационные примитивы ОС (там шла дискуссия по поводу boost::lightweight_mutex). WH>А можно ее сюда процитировать?
Если только в понедельник. Дома я ее не читаю, а подписываться ради этого лень.
... << RSDN@Home 1.1.4 beta 1 >>
"Будь достоин победы" (c) 8th Wizard's rule.
Re[5]: thread safe singlton - возможно ли такое в принципе
Здравствуйте, Lexey, Вы писали:
L>Здравствуйте, WolfHound, Вы писали:
L>>>Тут недавно в boost.develop появилась забавная нитка, в которой один перец довольно убедительно утверждает, что спин-локи, вообще говоря, довольно хреновая вещь, если у них нет fallback'а на синхронизационные примитивы ОС (там шла дискуссия по поводу boost::lightweight_mutex). WH>>А можно ее сюда процитировать?
L>Если только в понедельник. Дома я ее не читаю, а подписываться ради этого лень.
Ну держись, сейчас буду постить.
I believe there is a mistake in the 'details/lwm_gcc.hpp' and
'details/lwm_linux.hpp' (and possibly the other lightweight_mutex
implementation as well -- I haven't looked at them).
In both of these files we have a counter (m_.a_) that is initialized to 1.
The lock routine is implemented as follows:
while( !__exchange_and_add(&m_.a_, -1) ){
__atomic_add(&m_.a_, 1);
sched_yield();
}
The unlock as:
__atomic_add(&m_.a_, 1);
This works if there are only two contenders for the lock. It can fail if
there are three or more:
1- Thread one takes the lock, putting the counter at 0.
2- Thread two does the __exchange_and_add, fails the test, and then its time
time slice expires before the __atomic_add, leaving the counter at -1.
3- Thread three also takes the lock (because !-1 is false).
A simple solution is to initialize the counter to zero and replace the above
lock by:
while( __exchange_and_add(&m_.a_, 1) ){
__atomic_add(&m_.a_, -1);
sched_yield();
}
and the unlock by:
__atomic_add(&m_.a_, -1);
This would result in:
1- Thread one takes the lock, putting the counter at 1.
2- Thread two does the __exchange_and_add, fails the test, and then its time
slice expires before it can do the __atomic_add, leaving the counter at 2.
3- Thread cannot take the the lock (because 2 is true).
- -T
- --
Tyson Whitehead (-twhitehe@uwo.ca -- WSC-)
Computer Engineer Dept. of Applied Mathematics,
Graduate Student- Applied Mathematics University of Western Ontario,
GnuPG Key ID# 0x8A2AB5D8 London, Ontario, Canada
---------------------------------------------------------------------------------------------------------------------
Tyson Whitehead wrote:
> I believe there is a mistake in the 'details/lwm_gcc.hpp' and
'details/lwm_linux.hpp' (and possibly the other lightweight_mutex
implementation as well -- I haven't looked at them).
I am not sure what the status of these are. Are other components
supposed to be using these? In addition to what you mentioned, they're
broken in other ways:
> In both of these files we have a counter (m_.a_) that is initialized to 1.
The lock routine is implemented as follows:
while( !__exchange_and_add(&m_.a_, -1) ){
__atomic_add(&m_.a_, 1);
sched_yield();
If a low priority thread is preempted by a high priority thread
immediately before __atomic_add, and the high priority thread is
attempting to grab the spinlock, deadlock is acheived, despite the
sched_yield().
I think that spinlocks should not be used in a way other than as an
optimization for the non-contended case in an algorithm with fallback to
a real synchronization primative. I think it is a bug to do otherwise
on most non-realtime systems.
Aaron W. LaFramboise
-------------------------------------------------------------------------------------------------------------------------
Aaron W. LaFramboise wrote:
> I am not sure what the status of these are. Are other components
supposed to be using these?
I came upon them when I was checking out the smart pointers implementation.
Grepping the source seems to indicate 'lightweight_mutex.hpp' is only used by
the smart pointers (only included by 'quick_allocator.hpp' and
'shared_count.hpp') under multithreaded conditions.
> In addition to what you mentioned, they're broken in other ways:
If a low priority thread is preempted by a high priority thread
immediately before __atomic_add, and the high priority thread is
attempting to grab the spinlock, deadlock is acheived, despite the
sched_yield().
I missed that one. You're right though, and not only that, but the
sched_yield manpage states (I'm assuming this applies to threads -- threads
in Linux are actually processes -- right?):
"Note: If the current process is the only process in the highest priority list
at that time, this process will continue to run after a call to
sched_yield."
The implementer seemed to be aware there could be priority problems though.
The relevant comments in 'lightweight_mutex.hpp' are:
// * Used by the smart pointer library
// * Performance oriented
// * Header-only implementation
// * Small memory footprint
// * Not a general purpose mutex, use boost::mutex, CRITICAL_SECTION or
// pthread_mutex instead.
// * Never spin in a tight lock/do-something/unlock loop, since
// lightweight_mutex does not guarantee fairness.
// * Never keep a lightweight_mutex locked for long periods.
//
// The current implementation can use a pthread_mutex, a CRITICAL_SECTION,
// or a platform-specific spinlock.
//
// You can force a particular implementation by defining
// BOOST_LWM_USE_PTHREADS,
// BOOST_LWM_USE_CRITICAL_SECTION, or
// BOOST_LWM_USE_SPINLOCK.
//
// If neither macro has been defined, the default is to use a spinlock on
// Win32, and a pthread_mutex otherwise.
//
// Note that a spinlock is not a general synchronization primitive. In
// particular, it is not guaranteed to be a memory barrier, and it is
// possible to "livelock" if a lower-priority thread has acquired the
// spinlock but a higher-priority thread is spinning trying to acquire the
// same lock.
//
// For these reasons, spinlocks have been disabled by default except on
// Windows, where a spinlock can be several orders of magnitude faster than a
// CRITICAL_SECTION.
The current Win32 doesn't have the counter problems, and claims to be able to
yield to lower priority threads, so it should be okay. The relevant lock
routine is (m_.l_ is initialized to 0):
while( InterlockedExchange(&m_.l_, 1) ){
// Note: changed to Sleep(1) from Sleep(0).
// According to MSDN, Sleep(0) will never yield
// to a lower-priority thread, whereas Sleep(1)
// will. Performance seems not to be affected.
Sleep(1);
}
The unlock routine is:
InterlockedExchange(&m_.l_, 0);
- -T
- --
Tyson Whitehead (-twhitehe@uwo.ca -- WSC-)
Computer Engineer Dept. of Applied Mathematics,
Graduate Student- Applied Mathematics University of Western Ontario,
GnuPG Key ID# 0x8A2AB5D8 London, Ontario, Canada
-------------------------------------------------------------------------------------------
Tyson Whitehead wrote:
> The current Win32 doesn't have the counter problems, and claims to be able to
yield to lower priority threads, so it should be okay. The relevant lock
routine is (m_.l_ is initialized to 0):
while( InterlockedExchange(&m_.l_, 1) ){
// Note: changed to Sleep(1) from Sleep(0).
// According to MSDN, Sleep(0) will never yield
// to a lower-priority thread, whereas Sleep(1)
// will. Performance seems not to be affected.
Sleep(1);
}
For what its worth, while it avoids deadlocking problems, this code is
also wrong.
Synchronization code should never sleep. That code above will actually
sleep for the minimum amount of time Windows allows, which is usually
10ms. I measured the performance of this spinlock a while back when
improvements to GCC's Windows mutexes were being discussed on the MinGW
lists, and in some cases where there is substanctial contention over
short-lived locks, the performance degradation is quite unacceptable.
Certain naïve benchmarks might not notice, though, because during the
period waiters are sleeping, the lock will be accessable.
I think the code above could lead to severe slowdowns in some usage
cases, and could lead to difficult to diagnose intermittant 'blips' of
bad performance in others.
In any case, my point is that spinlocks are always going to be wrong in
these sorts of situations unless coupled with some other primative. In
fact, spinning at all is usually the wrong thing to do. On single CPU
systems, it is always a waste of precious time. On multi CPU systems,
it can cause very undesirable memory bus slowdowns.
The best general locking strategy I have seen--which is used by Linux
futexes and GCC for Windows' new locks--is where a normal scheduler
locking primative is wrapped by a quick userspace atomic operation to
avoid locking in the noncontended case. The downside is that, on
Windows for example, this will require 8 bytes of storage. I'm not sure
how shared_ptr uses mutexes, but this might not be acceptable.
These spinlocks need to be fixed. Is anyone working on this?
Aaron W. LaFramboise
-------------------------------------------------------------------------------------
Aaron,
Before taking your word for it, I would like to hear more about how your
benchmarks relate to smart pointers.
It sounds reasonable that the kind of lock described will perform badly in
high-contention situations. But what evidence do you have that there is any
probability of high contention, especially when, as you say, the locks are
short-lived? These locks aren't taken every time the smart pointer is accessed;
they're only taken when a copy is made. So to get the high contention you speak
of, you would have to have two threads repeatedly making copies of the "same"
smart pointer. From where I stand, this seems sufficiently rare that the extra
performance gain in the common cases is worth it.
/Mattias
-------------------------------------------------------------------------------------
flodin@cs.umu.se wrote:
> It sounds reasonable that the kind of lock described will perform badly in
high-contention situations. But what evidence do you have that there is any
probability of high contention, especially when, as you say, the locks are
short-lived? These locks aren't taken every time the smart pointer is accessed;
they're only taken when a copy is made. So to get the high contention you speak
of, you would have to have two threads repeatedly making copies of the "same"
smart pointer. From where I stand, this seems sufficiently rare that the extra
performance gain in the common cases is worth it.
[these comments are specific to the win32 spinlock discussed]
I have two specific concerns.
1) Personally, I value predictable performance. I would prefer slight
performance degradation (Note, however, that I am not examining the
actual numbers.) to the case where I may have an occasional very long
wait. (Specific example: If a graphically intensive program is aiming
for 30 frames per second, that is only about 30ms per frame. It would
be very unfortunate if, even occasionally, a single smart pointer copy
accounted for 1/3 of the time it took to render a frame.)
2) In "thundering herd" situations, contention is unavoidable, and
performance may be paramount. The possibility of a single thread
halting many other threads for 10ms on a machine with many processors is
disturbing. (Specific example: In a case where thread is producing work
for many consumer threads on a multiprocessor machine, and obtaining the
work involves copying a smart pointer, it is possible that some amount
of threads will forced to sleep every single time, which might be a
substantial percentage of total job wall clock time.)
In any case, I feel fairly strongly that sleeping is not the right thing
to do. Despite the weakness in appeal to authority, I will note that I
have seen Butenhof make the same assertion on Usenet, surely in a manner
more eloquent than mine.
I previously neglected to suggest alternatives.
1) A generally sound 8-byte mutex could be used. I am not sure how bad
this would be in terms of storage efficiency. It may be possible to
implement it using less storage, but I have no specific information on this.
2) I've seen a shared_ptr count implementation that used
InterlockedIncrement instead of the lwm code. I have not examined this
in detail; however it seems that an approach like this is better in all
respects than trying to manage locks if the only need is to maintain a
count (which is what many mutex primatives are doing anyway). Is there
some reason this cannot be used?
What do you think?
Aaron W. LaFramboise
--------------------------------------------------------------------------------
и т.д. Как потом выяснилось, насчет 10 ms, он все-таки загнул. Но народ вроде сошелся на том, что критичексая секция все-таки получше будет.
"Будь достоин победы" (c) 8th Wizard's rule.
Re[6]: thread safe singlton - возможно ли такое в принципе
L>и т.д. Как потом выяснилось, насчет 10 ms, он все-таки загнул. Но народ вроде сошелся на том, что критичексая секция все-таки получше будет.
Естественно, любой практически системный сервис, предоставляемый OS будет лучше (ну или _должен_ быть лучше). Но..
Каким образом Вы сможете использовать критическую секцию в приведенном синглетоне? Ее надо сначала проинициализировать — pkunzip.zip получается. Это во-первых. А во-вторых — производительность auto_lock нас не волнует, поскольку используется двойная проверка с memory barrier — т.о. в блок с auto_lock мы будем попадать только на этапе инициализации объекта синглтона, да и то скорее всего один раз. Единственная альтернатива (критические секции и неименованные мутексы не подойдут по приведенным выше соображениям) — это именованый мутекс. Но я сильно сомневаюсь, что поиск объекта по имени работает достаточно быстро (настолько быстро, чтобы быть существенно быстрее представленной реализации). В общем, в данном применении это наиболее оптимальный вариант lightweight мутекса, наверное. К тому же не засоряется пространство имен объектов.
Здравствуйте, Andrew S, Вы писали:
L>>и т.д. Как потом выяснилось, насчет 10 ms, он все-таки загнул. Но народ вроде сошелся на том, что критичексая секция все-таки получше будет. AS>Естественно, любой практически системный сервис, предоставляемый OS будет лучше (ну или _должен_ быть лучше). Но.. AS>Каким образом Вы сможете использовать критическую секцию в приведенном синглетоне? Ее надо сначала проинициализировать — pkunzip.zip получается. Это AS> во-первых. А во-вторых — производительность auto_lock нас не волнует, поскольку используется двойная проверка с memory barrier — т.о. в блок с auto_lock мы будем попадать только на этапе инициализации объекта синглтона, да и то скорее всего один раз. Единственная альтернатива (критические секции и неименованные мутексы не подойдут по приведенным выше соображениям) — это именованый мутекс. Но я сильно сомневаюсь, что поиск объекта по имени работает достаточно быстро (настолько быстро, чтобы быть существенно быстрее представленной реализации). В общем, в данном применении это наиболее оптимальный вариант lightweight мутекса, наверное. К тому же не засоряется пространство имен объектов.
В общем, я это и так знаю. И именованный мьютекс будет в подавляющем числе случаев будет существенно медленнее spin lock'a. Но, речь о том, что наверное самым правльным решением тут будет сочетание обоих приемов — spin lock с ограничением числа прокруток и без sleep'а + именованный мьютекс.
... << RSDN@Home 1.1.4 beta 1 >>
"Будь достоин победы" (c) 8th Wizard's rule.
Re[8]: thread safe singlton - возможно ли такое в принципе
Здравствуйте, Lexey, Вы писали:
L>В общем, я это и так знаю. И именованный мьютекс будет в подавляющем числе случаев будет существенно медленнее spin lock'a. Но, речь о том, что наверное самым правльным решением тут будет сочетание обоих приемов — spin lock с ограничением числа прокруток и без sleep'а + именованный мьютекс.
А в чем понт в спинлоке без слипа на однопроцессорной машине?
... << RSDN@Home 1.1.3 beta 1 >>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[9]: thread safe singlton - возможно ли такое в принципе
Здравствуйте, WolfHound, Вы писали:
L>>В общем, я это и так знаю. И именованный мьютекс будет в подавляющем числе случаев будет существенно медленнее spin lock'a. Но, речь о том, что наверное самым правльным решением тут будет сочетание обоих приемов — spin lock с ограничением числа прокруток и без sleep'а + именованный мьютекс. WH> А в чем понт в спинлоке без слипа на однопроцессорной машине?
Хм, а в чем понт слипа на однопроцессорной машине? Я вот не могу себе представить, зачем он в этом случае может быть нужен. Если происходит context switch, а lock все еще не отпущен другим потоком, то какой смысл делать слип, если можно сразу делать kernel wait? По хорошему, на однопроцессорной машине спин-лок вообще не нужен (критическая секция, кстати, его и игнорирует на однопроцессорных машинах).
... << RSDN@Home 1.1.4 beta 1 >>
"Будь достоин победы" (c) 8th Wizard's rule.
Re[10]: thread safe singlton - возможно ли такое в принципе
Здравствуйте, Lexey, Вы писали:
L>Хм, а в чем понт слипа на однопроцессорной машине? Я вот не могу себе представить, зачем он в этом случае может быть нужен. Если происходит context switch, а lock все еще не отпущен другим потоком, то какой смысл делать слип, если можно сразу делать kernel wait? По хорошему, на однопроцессорной машине спин-лок вообще не нужен (критическая секция, кстати, его и игнорирует на однопроцессорных машинах).
Быть может я чего не понимаю но если будет так:
поток 1 начал создавать объект и случилось переключение контекста
поток 2 захотел получить доступ к объекту но объект все еще залочен в этом случае Sleep форсирует переключение контекста
или я чего не понял?
... << RSDN@Home 1.1.3 beta 1 >>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[11]: thread safe singlton - возможно ли такое в принципе
Здравствуйте, WolfHound, Вы писали:
L>>Хм, а в чем понт слипа на однопроцессорной машине? Я вот не могу себе представить, зачем он в этом случае может быть нужен. Если происходит context switch, а lock все еще не отпущен другим потоком, то какой смысл делать слип, если можно сразу делать kernel wait? По хорошему, на однопроцессорной машине спин-лок вообще не нужен (критическая секция, кстати, его и игнорирует на однопроцессорных машинах). WH>Быть может я чего не понимаю но если будет так: WH>поток 1 начал создавать объект и случилось переключение контекста WH>поток 2 захотел получить доступ к объекту но объект все еще залочен в этом случае Sleep форсирует переключение контекста WH>или я чего не понял?
Все так, только какой смысл тут делать sleep, если можно сразу в kernel wait уйти.
... << RSDN@Home 1.1.4 beta 1 >>
"Будь достоин победы" (c) 8th Wizard's rule.
Re[12]: thread safe singlton - возможно ли такое в принципе
Здравствуйте, Andrew S, Вы писали:
L>>Все так, только какой смысл тут делать sleep, если можно сразу в kernel wait уйти. AS>Пример кода приведите, который в kernel wait уходит?
Создание именованного ивента + Wait на нем.
AS>Вам тут надо переключить конекст (т.е. усыпить тред до следующего shedule) — в виндовс это делается при помощи Sleep.
Wait как раз и вызовет переключение контекста.
... << RSDN@Home 1.1.4 beta 1 >>
"Будь достоин победы" (c) 8th Wizard's rule.
Re[14]: thread safe singlton - возможно ли такое в принципе
L>>>Все так, только какой смысл тут делать sleep, если можно сразу в kernel wait уйти. AS>>Пример кода приведите, который в kernel wait уходит?
L>Создание именованного ивента + Wait на нем.
Вообще то тут мы от этого пытаемся избавиться Посмотрите внимательно код синлтона с двойной проверкой. Сначала идет "грубая" быстрая проверка, просто на interlocked. Далее может идти все что угодно, что обеспечит мемори барриер до и после конструирования объекта и серийность входа.
AS>>Вам тут надо переключить конекст (т.е. усыпить тред до следующего shedule) — в виндовс это делается при помощи Sleep.
L>Wait как раз и вызовет переключение контекста.
Sleep(1) тоже. А если слипа не будет — то тред, попавший в цикл по interlocked, не будет отдавать время и соотв, будет потреблять все процессорное время. Думаю, пользователям (да и инициализирующемуся в это время объекту синглтона) это не очень понравится А особенно не понравится тем, кто будет выполнять сей код на win98, где тред, крутящийся в таком цикле, очень хорошо затормозит систему и растянет процесс инициализации объекта синглтона в другом треде на ооооочень продолжительное время.
И, еще раз — приведенный код (с auto_lock) будет вызываться от силы раз-два, и то при первом обращении к синглтону. Смысл создавать именованый kernel объект и засорять тем самым пространство имен? В общем, повторюсь, предложенный вариант будет вполне корректно работать на win32 (и, вероятно, с yield вместо sleep на linux — тут я не уверен, и прочитка топика, который привели вы, выяснила только одну проблему — что в линксовской используется счетчик (__atomic_add), а не просто обмен значениями, как в реализации, приведенной для win32 WolfHound'ом. Но это уже проблемы ДНК тех, кто писал эту реализацию, по моему глубокому убеждению. I love gnu libs code
В общем — главный плюс этого кода не то, что он быстрее или медленнее нативных именованых объектов, предоставляемых ОС, а то, что он их собственно не требует, позволяя не засорять пространство имен на одноразовое фактически использование.
Здравствуйте, Andrew S, Вы писали:
L>>>>Все так, только какой смысл тут делать sleep, если можно сразу в kernel wait уйти. AS>>>Пример кода приведите, который в kernel wait уходит?
L>>Создание именованного ивента + Wait на нем.
AS>Вообще то тут мы от этого пытаемся избавиться Посмотрите внимательно код синлтона с двойной проверкой. Сначала идет "грубая" быстрая проверка, просто на
Я его в общем-то вполне нормально смотрел.
AS> interlocked. Далее может идти все что угодно, что обеспечит мемори барриер до и после конструирования объекта и серийность входа.
А я и не предлагаю убирать interlock и двойную, я предлагаю заменить спин-лок на kernel wait.
AS>>>Вам тут надо переключить конекст (т.е. усыпить тред до следующего shedule) — в виндовс это делается при помощи Sleep.
L>>Wait как раз и вызовет переключение контекста.
AS>Sleep(1) тоже. А если слипа не будет — то тред, попавший в цикл по interlocked, не будет отдавать время и соотв, будет потреблять все процессорное время. Думаю, пользователям (да и инициализирующемуся в это время объекту синглтона) это не очень понравится А особенно не понравится тем, кто будет выполнять сей код на win98, где тред, крутящийся в таком цикле, очень хорошо затормозит систему и растянет процесс инициализации объекта синглтона в другом треде на ооооочень продолжительное время.
А я где-то предполагал другое?
AS>И, еще раз — приведенный код (с auto_lock) будет вызываться от силы раз-два, и то при первом обращении к синглтону. Смысл создавать именованый kernel объект и
Далеко не факт. Если синглтон долго инициализируется, то вызываться он может много раз.
AS> засорять тем самым пространство имен? В общем, повторюсь, предложенный вариант
Ты боишься засорить пространство имен одним именованным ивентом? Это как-то совсем несерьезно звучит.
AS> будет вполне корректно работать на win32 (и, вероятно, с yield вместо sleep на linux — тут я не уверен, и прочитка топика, который привели вы, выяснила только одну проблему — что в линксовской используется счетчик (__atomic_add), а не просто обмен значениями, как в реализации, приведенной для win32 WolfHound'ом. Но это уже проблемы ДНК тех, кто писал эту реализацию, по моему глубокому убеждению. I love
В таком случае проблемы в ДНК есть у всех программистов.
AS> gnu libs code
В общем-то, этот топик кроме всего прочего обсуждал как раз вредность чистых спин-локов. Именно эту идею я и пытаюсь отстаивать. Кстати, я не привел конец этой дискуссии, так вот там народ сошелся на том, что lightweight_mutex пригоден только для очень коротких операций. В остальных случаях его лучше не использовать. Ты можешь гарантировать, что инициализация синглетона — быстрый процесс?
AS> В общем — главный плюс этого кода не то, что он быстрее или медленнее нативных именованых объектов, предоставляемых ОС, а то, что он их собственно не требует, позволяя не засорять пространство имен на одноразовое фактически использование.
Это как раз совершенно не серьезное преимущество.
В общем, я могу предложить вариант, который наверное устроит и тебя и меня.
Сделать синглетон-критическую секцию, создание которой защитить lightweight_mutex'ом (создание тут быстрое, так что можно). И вот эту критическую секцию и использовать при инициализации остальных синглетонов.
... << RSDN@Home 1.1.4 beta 1 >>
"Будь достоин победы" (c) 8th Wizard's rule.
Re[16]: thread safe singlton - возможно ли такое в принципе
L>Это как раз совершенно не серьезное преимущество. L>В общем, я могу предложить вариант, который наверное устроит и тебя и меня. L>Сделать синглетон-критическую секцию, создание которой защитить lightweight_mutex'ом (создание тут быстрое, так что можно). И вот эту критическую секцию и использовать при инициализации остальных синглетонов.
Одну критическую секция на все? Несерьезно, тормоза будут дикие, более того может быть так, что создание одного синглтона зависит от другого, и это происходит в разных потоках. Придется все-равно по одному именованному объекту на синглтон. Мне кажется, это не очень хорошая мысль. А точнее, очень _не_ хорошая. Впрочем, каждому свое
В любом случае, серебряной пули не существует...
Здравствуйте, Lexey, Вы писали:
AS>>Одну критическую секция на все? Несерьезно, тормоза будут дикие, более того может L>Совсем не обязательно. Можно, например, сделать шаблонную "фабрику" критических секций.
Гм??? А можно подробней?
... << RSDN@Home 1.1.3 beta 1 >>
Пусть это будет просто:
просто, как только можно,
но не проще.
(C) А. Эйнштейн
Re[19]: thread safe singlton - возможно ли такое в принципе
AS>>>Одну критическую секция на все? Несерьезно, тормоза будут дикие, более того может L>>Совсем не обязательно. Можно, например, сделать шаблонную "фабрику" критических секций. WH>Гм??? А можно подробней?
Как я понял, предлагается сделать синглтон (точнее, даже, фабрику), производящую объекты — критические секции По одной на каждый шаблонный класс синглтона. А уже ими пользоваться во второй проверке вместо авто лока (т.е. при захвате объека критической секции эта фабрика будет, защищаясь авто локом, в случае необходимости инициализировать критическую секцию — поскольку это быстро). Т.о. проблема частично решена введением дополнительного уровня абстракции. Остается дождаться, что кто-нибудь захочет поломать голову и найти баги в этой мысли — тогда придется вводить еще один уровень абстракции — фабрику фабрик критических секций и т.п.
Немного пояснений.
Как я понял — основные претензии к реализации авто лока у Lexey — это то, что "защищенный им код должен выполняться очень непродолжительное время". Это мягко говоря не так в случае win32 — там все вполне пристойно. А вот в линукс реализации используется декремент\инкремент при каждой "прокрутке", т.е. операция захвата выполняется не атомарно.
while( __exchange_and_add(&m_.a_, 1) ){
// если в этот момент другой тред будет пытаться захватить автолок, наверняка будет бяка
__atomic_add(&m_.a_, -1);
// и в этот наверняка тоже :)
sched_yield();
}
Но повторюсь, это конкретная реализация. Я, к сожалению, не знаю линукс настолько, чтобы судить о возможности там сделать по-другому (т.е. не инкрементом, а обменом), но, очевидно, операция установки флага "занятости" автолока должна быть атомарной, ни в первом, ни во втором