Всем привет.
Вопрос по поводу реализации многопоточной защиты патерна singleton, везде описаны средтсва как защитить сам instance от опроса разными тредами, но как быть увереным, что сам объект синхронизации находится в валидном состоянии ?
Вот как пример
Если какой то объект будет создан раньше того, как будет вызван этот Instance, и в своем, скажем деструкторе дернет этот Instance, то по идее instanceMutex будет уже не валидным Как можно защитится от этого ?
Здравствуйте, Аноним, Вы писали:
А>Всем привет. А>Вопрос по поводу реализации многопоточной защиты патерна singleton, везде описаны средтсва как защитить сам instance от опроса разными тредами, но как быть увереным, что сам объект синхронизации находится в валидном состоянии ? А>Вот как пример
А>
А>Если какой то объект будет создан раньше того, как будет вызван этот Instance, и в своем, скажем деструкторе дернет этот Instance, то по идее instanceMutex будет уже не валидным Как можно защитится от этого ?
А>спс.
ИМХО проблема вашей реализации в вызове ms_instance = new InstanceType(); Возможна ситуация когда в ms_instance будет занесено значение ДО ТОГО как выполнятся все конструкторы класса InstanceType. Если в это момент другой поток вызовет getInstance то возможно возвращение не полностью валидного ms_instance. В новом стандарте C++ это решается объявлением ms_instance как volatile.
Здравствуйте, Аноним, Вы писали:
A> Возможна ситуация когда в ms_instance будет занесено значение ДО ТОГО как выполнятся все конструкторы класса InstanceType.
Каким образом?
Re[2]: Как защитить mutex singletona
От:
Аноним
Дата:
06.04.11 18:46
Оценка:
Здравствуйте, Caracrist, Вы писали:
C>Здравствуйте, Аноним, Вы писали:
А>> Как можно защитится от этого ? C>
В вашей ситуации все еще хуже, сначала могут быть обращения к CreateInstance а потом только инициализация
template <typename T> SIMPLE_MUTEX Singletone<T>::m_mutex; Помимо оставшейся ситуации, когда объект был создан раньше template <typename T> SIMPLE_MUTEX Singletone<T>::m_mutex , и имеет возможность дернуть метод уже после разрушения m_mutex. Не говоря уже о том, что создание статической переменной не гаранировано ПОСЛЕ захвата мутекса, она же статическая, я так понимаю компилятор спокойно сможет начать строить ее при первом обращении к функции, а потом туда влезет другой поток... и что тогда ?
Здравствуйте, Аноним, Вы писали:
А>Если какой то объект будет создан раньше того, как будет вызван этот Instance, и в своем, скажем деструкторе дернет этот Instance, то по идее instanceMutex будет уже не валидным Как можно защитится от этого ?
Долго вчитывался, так и не понял, что конкретно имелось ввиду
У вас instanceMutex объявлен static. Поменяли шило на мыло.
С таким же успехом можно было заюзать:
Другие варианты:
1) сделать мьютекс статическим членом класса, но при этом нужна гарантия, что до входа в main() getInstance() вызываться не будет (второй Аноним уже намекнул на это);
2) использовать счетчики Шварца, если нужно дергать getInstance() когда угодно.
Отвечая второму Анониму (предположу, что звать Олегом? ) :
Здравствуйте, Аноним, Вы писали:
А>ИМХО проблема вашей реализации в вызове ms_instance = new InstanceType(); Возможна ситуация когда в ms_instance будет занесено значение ДО ТОГО как выполнятся все конструкторы класса InstanceType. Если в это момент другой поток вызовет getInstance то возможно возвращение не полностью валидного ms_instance. В новом стандарте C++ это решается объявлением ms_instance как volatile.
Что касается указанной проблемы синглтона. Если не использовать новый стандарт, тогда вариантов немного: использовать временную переменную, а потом самопальными мембарами и compare_and_swap с ms_instanse.
Неоконченная мысль всегда казалась Шри Япутре слишком
Здравствуйте, баг, Вы писали:
баг>Здравствуйте, Аноним, Вы писали:
A>> Возможна ситуация когда в ms_instance будет занесено значение ДО ТОГО как выполнятся все конструкторы класса InstanceType. баг>Каким образом?
Это как минимум три разных операции (если смотреть не самый низкий уровень): выделение памяти, вызов конструкторов, запись в переменную. Последние два компилятор/процессор может распараллелить или поменять местами. Временной отрезок очень мал, но он есть. И новый вродебы и правда решает проблему.
Да и в текущем C++ не помешает volatile, так как идет два чтения переменной подряд.
Re[2]: Как защитить mutex singletona
От:
Аноним
Дата:
09.04.11 16:31
Оценка:
Здравствуйте, Kolobrodin, Вы писали:
K>Здравствуйте, Аноним, Вы писали:
А>>Если какой то объект будет создан раньше того, как будет вызван этот Instance, и в своем, скажем деструкторе дернет этот Instance, то по идее instanceMutex будет уже не валидным Как можно защитится от этого ?
K>Долго вчитывался, так и не понял, что конкретно имелось ввиду
K>У вас instanceMutex объявлен static. Поменяли шило на мыло. K>С таким же успехом можно было заюзать:
K>static InstanceTypeRef getInstance() K>{ K> static InstanceType s_instance; K> return s_instance; K>}
K>И надо сказать, это кое-где работает
K>Другие варианты: K>1) сделать мьютекс статическим членом класса, но при этом нужна гарантия, что до входа в main() getInstance() вызываться не будет (второй Аноним уже намекнул на это); K>2) использовать счетчики Шварца, если нужно дергать getInstance() когда угодно.
K>Отвечая второму Анониму (предположу, что звать Олегом? ) :
K>Здравствуйте, Аноним, Вы писали:
А>>ИМХО проблема вашей реализации в вызове ms_instance = new InstanceType(); Возможна ситуация когда в ms_instance будет занесено значение ДО ТОГО как выполнятся все конструкторы класса InstanceType. Если в это момент другой поток вызовет getInstance то возможно возвращение не полностью валидного ms_instance. В новом стандарте C++ это решается объявлением ms_instance как volatile.
K>Что касается указанной проблемы синглтона. Если не использовать новый стандарт, тогда вариантов немного: использовать временную переменную, а потом самопальными мембарами и compare_and_swap с ms_instanse.
О, спс, за внимание, уже не ожидал, что по теме что либо всплывет
Меня зовут, не Олег, незнаю уж, как вы так решили догадатся Федя, очень приятно K>Долго вчитывался, так и не понял, что конкретно имелось ввиду
Ситуация такая, проблема KDL (keyboard, dysplay, log), как у александреску, тока при многопоточном доступе.
вот пример, на коленке написаный
struct mutex
{
criticalsection_or_some_other * m;
~mutex(){ uninit(m); } // 1.
mutex(){ init(m); } // 2.
lock(){ lock(m);} // 3.
unlock(){ unlock(m);} // 4.
};
struct lock
{
mutex & m;
~lock(){ m.unlock(); } // 5.
lock( mutex & m):m(m){ m.lock(); } // 6.
};
template< typename T >
class singleton
{
static T * volatile ms_instance;
static mutex ms_instanceMutex;
public:
T & getInstance()
{
if( NULL == ms_instance )
{
lock sentinel( ms_instanceMutex ); // 7.if( NULL == ms_instance )
{
ms_instance = new T();
}
}
return * ms_instance;
}
};
tempate< typename T >
T * volatile singleton< T >::ms_instance; // 8.
tempate< typename T >
mutex singleton< T >::ms_mutex; // 9.
// теперь пошел хаос с файлами проекта, и прочим, где то имеем#include <logsingleton>
struct Test
{
Test()
{
singleton< log >::getInstance().write( "foo" );
}
};
struct TestEnter
{
TestEnter()
{
Test t;
}
};
struct TestExit
{
~TestExit()
{
Test t;
}
};
testExit g_t1;
// проблема 1
// если конструирование этого объекта начнется раньше, чем 9., то в деструкторе будет вызвано 7. а там будет вызвано 6. а там деинициализированое значение барьера
testEnter g_t2;
// проблема 2
// если конструирование этого объекта начнется раньше, чем 9., то в конструторе будет вызвано 7. а там будет вызвано 6. а там не инициализированое значение барьера, там только память выделеная под него
// проблема 3
// понять когда instance надо удалить
В общем видно, что проблемы получить instance как таковой не стоит, получить еге легко и просто и если не использовать межпотоковую синхронизацию, то есть только одна проблема, когда прибить синглетон, этот вопрос обмусолен со всех сторон у александреску, так что я его не поднимаю. Остается решить две проблемы, каждая из которых в принципе требует одного и того же, чтобы объект синхронизации был гарантировано сконструирован ДО первого обращения к getInstance. Счетчик шварца насколько я понял, просто будет засорять каждый юнит трансляции еще одним статическим объектом, который непонятно, что будет делать, веть создавать инстанс синглетона от него не требуется, это надо делать только в случае необходимости.
Вот как то так. Если это не проблема, то хотелось бы знать это, если это уже имеет решение, то тоже не отказался бы вкурить пруф. Пока реализовал свою защиту, то она также имеет слабое место.
Здравствуйте, Аноним, Вы писали:
А>Федя, очень приятно
Понятно, просто в таком же контексте точно такое же мне не так давно говорил один человек, вот и подумал.
А>В общем видно, что проблемы получить instance как таковой не стоит, получить еге легко и просто и если не использовать межпотоковую синхронизацию, то есть только одна проблема, когда прибить синглетон, этот вопрос обмусолен со всех сторон у александреску, так что я его не поднимаю. Остается решить две проблемы, каждая из которых в принципе требует одного и того же, чтобы объект синхронизации был гарантировано сконструирован ДО первого обращения к getInstance. Счетчик шварца насколько я понял, просто будет засорять каждый юнит трансляции еще одним статическим объектом, который непонятно, что будет делать, веть создавать инстанс синглетона от него не требуется, это надо делать только в случае необходимости.
Здесь проблема в контроле порядка создания статических переменных в разных юнитах, что вообщем-то неопределено, но какими-то трюками либо "волевыми решениями" можно добиться нужного результата. Счетчик шварца не будет создавать инстанс. Он будет создавать мьютекс. И только один.
А>Вот как то так. Если это не проблема, то хотелось бы знать это, если это уже имеет решение, то тоже не отказался бы вкурить пруф. Пока реализовал свою защиту, то она также имеет слабое место.
Неоконченная мысль всегда казалась Шри Япутре слишком
K>Здесь проблема в контроле порядка создания статических переменных в разных юнитах, что вообщем-то неопределено, но какими-то трюками либо "волевыми решениями" можно добиться нужного результата. Счетчик шварца не будет создавать инстанс. Он будет создавать мьютекс. И только один.
Под "волевыми решениями" я подразумеваю что-то типа: собрать все глобальные переменные в одном месте и упорядочить.
Неоконченная мысль всегда казалась Шри Япутре слишком
Re[5]: Как защитить mutex singletona
От:
Аноним
Дата:
09.04.11 18:41
Оценка:
Здравствуйте, Kolobrodin, Вы писали:
K>>Здесь проблема в контроле порядка создания статических переменных в разных юнитах, что вообщем-то неопределено, но какими-то трюками либо "волевыми решениями" можно добиться нужного результата. Счетчик шварца не будет создавать инстанс. Он будет создавать мьютекс. И только один.
K>Под "волевыми решениями" я подразумеваю что-то типа: собрать все глобальные переменные в одном месте и упорядочить.
Я не много не понял, как будет работать счетчик шварца в конкретной задаче ? если он будет просто инициализировать мутекс при необходимости в каждой единице трансляции, то в этом нету смысла. Если есть другой тайный смысл, я бы его выслушал Сейчас делаю аналогично, подобному поведению, тока на мой взгляд более элегантно, использую синглетон Мейерса для создания прокси объекта указателя на инстанс и защищающий его мутекс, таким образом и сам инстанс и защищающий его мутекс повязаны вместе, нету возможности добратся до экземпляра без инициализации мутекса любому другому объекту даже глобальному. Так же решается проблема очередности, возможностью взятия адреса этого хранилища, что приведет к форсированию его создания, без создания инстанса. Что перекрывает все дыры, непонимаю, почему у александресу так сложно все было придумано через Lifetime, когда можно было просто гарантировано пропихнуть на стек более важный объект. Осталась тока одна дыра, которую вообще не представляю как решать ибо она какая то циклическая — это инициализация самого мутекса, если форсировать задержку через Sleep и долбится сразу другими потоками, то вроде прошибается. Просто если сделать еще одну обрамляющую секцию инициализации, то ее опять таки надо лочить статическим мутексом, и опять приходим к тому, что если не успеваем в первом потоке завершить полное создание муткса, то можем получить fail.
Эх, иметь бы некий 100% сконструированный объект синхронизации.
Здравствуйте, Аноним, Вы писали:
А>Я не много не понял, как будет работать счетчик шварца в конкретной задаче ? если он будет просто инициализировать мутекс при необходимости в каждой единице трансляции, то в этом нету смысла.
Здравствуйте, Kolobrodin, Вы писали:
K>Здравствуйте, Kolobrodin, Вы писали:
K>>Здравствуйте, Аноним, Вы писали:
K>Вот примерчик, выдрал из проекта:
K>.hpp:
K>namespace {
K>size_t g_ptr_index;
K>static char spinlock_data[sizeof(spinlock)];
K>spinlock* ptr = reinterpret_cast<spinlock*>(&spinlock_data);// NOTE: if make reinterpret_cast from
K>spinlock& g_spinlock = static_cast<spinlock&>(*ptr); // char* to spinlock& then will be warning
K> // [dereferencing type-punned pointer will
K> // break strict-aliasing rules]
K>} // unnamed namespace
K>size_t make_index()
K>{
K> scoped_lock<spinlock> lock(&g_spinlock);
K> return g_ptr_index++;
K>}
K>// Nifty counter.
K>size_t spec_ptr_initializer::s_nifty_counter;
K>// Nifty counter init.
K>spec_ptr_initializer::spec_ptr_initializer()
K>{
K> if (0 == s_nifty_counter++)
K> {
K> new (&spinlock_data) spinlock();
K> printf("%s", "Create spinlock\n");
K> }
K>}
K>// Nifty counter clean-up.
K>spec_ptr_initializer::~spec_ptr_initializer()
K>{
K> if (0 == --s_nifty_counter)
K> {
K> (&g_spinlock)->~spinlock();
K> printf("%s", "Destroy spinlock\n");
K> }
K>}
K>
Таким образом, make_index() можно использовать в других статических объектах.
Как сказано тут эта техника применяется для создания std::cout, std::cin, std::cerr, std::clog. Поэтому cout мы можем использовать в конструкторах глобальных объектов.
Неоконченная мысль всегда казалась Шри Япутре слишком
namespace {
size_t s_nifty_counter; // Nifty counter.
size_t g_ptr_index;
static char spinlock_data[sizeof(spinlock)];
spinlock* ptr = reinterpret_cast<spinlock*>(&spinlock_data); // NOTE: if make reinterpret_cast from
spinlock& g_spinlock = static_cast<spinlock&>(*ptr); // char* to spinlock& then will be warning
// [dereferencing type-punned pointer will
// break strict-aliasing rules]
}
Неоконченная мысль всегда казалась Шри Япутре слишком
Re[10]: Как защитить mutex singletona
От:
Аноним
Дата:
09.04.11 22:58
Оценка:
>Здравствуйте, Kolobrodin, Вы писали: >...
О, спс, за ссылку, как раз рассматриваются те вопросы что меня мучают
Вынес, походу одну самую важную вещь
Recommendation: Don't create threads during global object initialization.
А создание у вас глобального спин лока, понравилась, я так понял, что в каждом юните появится этот инициализатор, а так как инклюды обычно выше пишут, чем код, то он встанет в стек раньше, и проживет соответсвенно дольше , только не понял, почему его в статической памяти так усердно (многострочно) пытались разместить. И опять таки, нету гарантий, что scoped_lock<spinlock> lock(&g_spinlock); вызовется позже чем будет полный init. Кроме соответсвующий конвенции не вызывать потоки из конструкторов глобальных объектов
А еще зацепил кусок кода
Local &GetLocal()
{
static int guard; // Will be zeroed at load time
spin_mutex smx(&guard); // Spin on "guard"
lock_scope<spin_mutex> lock(smx); // Scope lock of "smx"static Local local;
return local;
}
который как раз у меня такой же, я все хотел как то зацепить выход из функции глобальным mutex но никак не вкурю как это, и тут вроде написано, как раз тоже самое, но что это за мутекс такой ? самопальный ? У Рихтера есть пример как сделать собственный spinlock на базе интрелокед функций, но имхо, это не тот случай, чтобы огород городить, если бы был "стандартный" то это было бы супер
Здравствуйте, Аноним, Вы писали:
>>Здравствуйте, Kolobrodin, Вы писали: >>...
А>О, спс, за ссылку, как раз рассматриваются те вопросы что меня мучают А>Вынес, походу одну самую важную вещь
А>
А>Recommendation: Don't create threads during global object initialization.
А>
Одна из самых важных рекомендаций.
А>А создание у вас глобального спин лока, понравилась, я так понял, что в каждом юните появится этот инициализатор, а так как инклюды обычно выше пишут, чем код, то он встанет в стек раньше, и проживет соответсвенно дольше , только не понял, почему его в статической памяти так усердно (многострочно) пытались разместить. И опять таки, нету гарантий, что scoped_lock<spinlock> lock(&g_spinlock); вызовется позже чем будет полный init. Кроме соответсвующий конвенции не вызывать потоки из конструкторов глобальных объектов
В догонку по теме : здесь С объяснением нюансов.
А>А еще зацепил кусок кода
А>
А>Local &GetLocal()
А>{
А> static int guard; // Will be zeroed at load time
А> spin_mutex smx(&guard); // Spin on "guard"
А> lock_scope<spin_mutex> lock(smx); // Scope lock of "smx"
А> static Local local;
А> return local;
А>}
А>
А>который как раз у меня такой же, я все хотел как то зацепить выход из функции глобальным mutex но никак не вкурю как это, и тут вроде написано, как раз тоже самое, но что это за мутекс такой ? самопальный ? У Рихтера есть пример как сделать собственный spinlock на базе интрелокед функций, но имхо, это не тот случай, чтобы огород городить, если бы был "стандартный" то это было бы супер