Здравствуйте, Кодт, Вы писали:
К>Разве что для надёжности можешь заменить неатомарные присваивания атомарными — InterlockedExchange / InterlockedCompareExchange.
InterlockedExchange тоже не хочу, прийдётся писать volatile и терять возможную оптимизацию при дальнейшем использовании. А что будет значить "для надёжности" ?
Здравствуйте, bkat, Вы писали:
B>Достаточно уже этого: B>
B> if (initialized)
B> return;
B> // тут даже не важно, что вообще инициируется
B> initialized = true;
B>
B>чтобы подумать о нормальной инициализации.
Давай подумаем о нормальной инициализации.
Требования:
1. Возможность вызова до main. Желательно чтобы была такая защищённость от дурака, чтобы работало даже если дополнительные потоки созданы до main.
2. Максимальная простота.
3. Максимальная переносимость.
4. Расположение в статической памяти.
Поэтому, откинем все эти boost::call_once или InitOnceXxx, синглтон Мейерса, другие известные реализации синглтона... и вроде ничего "нормального" не остаётся. И я не против если не нормальное оказывается лучше.
B>А мамой можно клянуться сколько угодно, но этих клятв хватает максимум до следующего релиза
Как бы эта штука вообще меняется в первый раз. И там нет ничего, что может нарушать клятву о полной повторимости каждой инициализации.
Здравствуйте, Alexander G, Вы писали:
AG>Здравствуйте, bkat, Вы писали:
B>>Достаточно уже этого: B>>
B>> if (initialized)
B>> return;
B>> // тут даже не важно, что вообще инициируется
B>> initialized = true;
B>>
B>>чтобы подумать о нормальной инициализации.
AG>Давай подумаем о нормальной инициализации. AG>Требования: AG>1. Возможность вызова до main. Желательно чтобы была такая защищённость от дурака, чтобы работало даже если дополнительные потоки созданы до main. AG>2. Максимальная простота. AG>3. Максимальная переносимость. AG>4. Расположение в статической памяти.
AG>Поэтому, откинем все эти boost::call_once или InitOnceXxx, синглтон Мейерса, другие известные реализации синглтона... и вроде ничего "нормального" не остаётся.
Как раз банальный синглтон, в котором бы честно синхронизировалось создание экземпляра,
было бы, на мой взгляд, простым решением.
Пусть твой синглтон и отвечает за такие вещи, как версия OS и прочие подобные вещи.
Проблема то стандартная. Чего тут то на блокировках экономить?
B>>И я не против если не нормальное оказывается лучше.
Лучше чего? Твое решение — это просто инициализация с полным игнорированием проблем многопоточных приложений.
Ну блокировки нет, зато гонки есть, и есть большая вероятность начать пользоваться
данными до того, как кто-то их проинициировал.
B>>А мамой можно клянуться сколько угодно, но этих клятв хватает максимум до следующего релиза
AG>Как бы эта штука вообще меняется в первый раз. И там нет ничего, что может нарушать клятву о полной повторимости каждой инициализации.
Ну тебе еще придется давать клятву о том, что каждый поток (включая главный поток) гарантированно вызывает
функцию init() до того, как он попытается использовать результаты инициализации.
Кстати, это может запросто случится даже без дополнительных потоков.
С учетом того, что глобальные данные в различных модулях инициализируются
в непредсказуемом порядке, ты можешь поиметь очень забавные побочные эффекты.
Здравствуйте, Alexander G, Вы писали:
AG>InterlockedExchange тоже не хочу, прийдётся писать volatile и терять возможную оптимизацию при дальнейшем использовании. А что будет значить "для надёжности" ?
Похоже, что ты экономишь даже не на спичках, а огрызках от спичек.
Здравствуйте, Alexander G, Вы писали:
К>>Разве что для надёжности можешь заменить неатомарные присваивания атомарными — InterlockedExchange / InterlockedCompareExchange. AG>InterlockedExchange тоже не хочу, прийдётся писать volatile и терять возможную оптимизацию при дальнейшем использовании.
volatile можешь присобачить по месту, в const_cast. Всё равно твой сценарий не предполагает, что там будет нечто постоянно меняющееся, так что пусть компилятор сколько угодно оптимизирует чтение после инициализации.
AG>А что будет значить "для надёжности" ?
На случай той самой экзотической платформы (или взбесившегося компилятора), который в процессе перезаписи повреждает переменную.
B>Как раз банальный синглтон, в котором бы честно синхронизировалось создание экземпляра, B>было бы, на мой взгляд, простым решением.
С помощью какого примитива синхронизации ?
B>Пусть твой синглтон и отвечает за такие вещи, как версия OS и прочие подобные вещи. B>Проблема то стандартная. Чего тут то на блокировках экономить?
Тут такое дело, что желательно избавиться от каждого вызова апи иоли библиотеки.
Вроде придумал так:
struct data_t
{
...
} data = {0};
long volatile initialized = FALSE;
void init()
{
if (!initialized)
return;
data_t local_data;
/* инициализация local_data */
...
if (::InterlockedExchange(&initialized, TRUE))
memcpy(&data, local_data, sizeof(data_t);
}
B>Ну тебе еще придется давать клятву о том, что каждый поток (включая главный поток) гарантированно вызывает B>функцию init() до того, как он попытается использовать результаты инициализации. B>Кстати, это может запросто случится даже без дополнительных потоков.
Это детали реализации, наружу будет немного методов, init в каждом из которых. Или наружу можно даже типа синглтона, с методом Instance.
Здравствуйте, Alexander G, Вы писали:
AG>Здравствуйте, bkat, Вы писали:
B>>Как раз банальный синглтон, в котором бы честно синхронизировалось создание экземпляра, B>>было бы, на мой взгляд, простым решением.
AG>С помощью какого примитива синхронизации ?
Да хоть с критической секцией.
B>>Пусть твой синглтон и отвечает за такие вещи, как версия OS и прочие подобные вещи. B>>Проблема то стандартная. Чего тут то на блокировках экономить?
AG>Тут такое дело, что желательно избавиться от каждого вызова апи иоли библиотеки.
AG>Вроде придумал так:
AG>
Плохо, очень плохо.
Ты можешь с таким же успехом выкинуть InterlockedExchange и это будет так же "надежно", как и твой вариант.
В момент, когда работает memcpy, для других потоков данные уже инициированы, хотя это не так.
Сделай лучше для начала типа этого:
struct data_t
{
static data_t* getInstance()
{
lock(); // например с помощью мьютексаstatic data_t* instance = new data_t;
unlock();
return instance;
}
};
Если getInstance() безбожно будет тормозить твою систему (в чем я сильно сомневаюсь),
то просто вызывай getInstance один раз на поток.
Будь проще, и не пытайся оптимизировать то, что скорей всего никогда не будет тормозить.
Здравствуйте, bkat, Вы писали:
AG>>С помощью какого примитива синхронизации ? B>Да хоть с критической секцией.
Покажи работающий синглтон с критичесской секцией
Это нетривиально.
B>>>Пусть твой синглтон и отвечает за такие вещи, как версия OS и прочие подобные вещи. B>>>Проблема то стандартная. Чего тут то на блокировках экономить?
AG>>Тут такое дело, что желательно избавиться от каждого вызова апи иоли библиотеки.
AG>> if (::InterlockedExchange(&initialized, TRUE)) AG>> memcpy(&data, local_data, sizeof(data_t); AG>>} AG>>[/ccode]
B>Плохо, очень плохо.
Здравствуйте, Alexander G, Вы писали:
AG>Здравствуйте, bkat, Вы писали:
AG>>>С помощью какого примитива синхронизации ? B>>Да хоть с критической секцией.
AG>Покажи работающий синглтон с критичесской секцией AG>Это нетривиально.
Дак я тебе же показал.
Нетривиально — это для тех, кто не хотят пользоваться критическими секциями.
Ты наверное имеешь ввиду дискуссии на тему типа этой: http://ru.wikipedia.org/wiki/Double_checked_locking
А то, что я тебе показал — это примитивное решение "в лоб", которое работает
Здравствуйте, Alexander G, Вы писали:
AG>нет, покажи полностью, с самой КС, чтобы было cs.Lock() или EnterCriticalSection(&cs); . AG>А то непонятно как то что было показано может работать.
Ну блин...
Народ с основами не разобрался, а уже многопоточные приложения пишет
Считай, QMutex в конструкторе инициализирует CRITICAL_SECTION
QMutexLocker в конструкторе вызывает EnterCriticalSection,
а в деструкторе — LeaveCriticalSection.
Здравствуйте, bkat, Вы писали:
B>Ну блин... B>Народ с основами не разобрался, а уже многопоточные приложения пишет
B>Вот, как бы это выглядело бы на Qt
B>
B>Считай, QMutex в конструкторе инициализирует CRITICAL_SECTION B>QMutexLocker в конструкторе вызывает EnterCriticalSection, B>а в деструкторе — LeaveCriticalSection.
Этот код не рабочий, т.к. getInstance может быть вызван до конструктора QMutex.
Здравствуйте, bkat, Вы писали:
B>Здравствуйте, Alexander G, Вы писали:
AG>>Этот код не рабочий, т.к. getInstance может быть вызван до конструктора QMutex.
B>Смотри внимателей. B>mutex — статический, а иначе ты бы его не смог пользовать в статическом методе getInstance.
Да. но порядок инициализации статических объектов не определён.
Конструкторы статических объектов ничем не лучше конструкторов глобальных объектов, и так же вызываются в неопределённом порядке.
т.е. в этом коде ничего нельзя сказать про порядок S1, S2, S3:
struct S { S() }
S s1;
struct X
{
static S s2;
}
S X::s2;
S s3;
Я знаю как ими пользоваться. Это ты похоже не понял проблему
Если бы всё было так просто, я бы действительно взял КС.
B>P.S. Конструкторы у объектов есть всегда.
Да, но может не быть пользовательских конструкторов. Тогда возможна статическая инициализаций, как с S1 ниже. Такая инициализация гарантирована до инициализации S2.
Здравствуйте, Alexander G, Вы писали:
AG>нет, покажи полностью, с самой КС, чтобы было cs.Lock() или EnterCriticalSection(&cs); . AG>А то непонятно как то что было показано может работать.
Щас попробую на коленке родить.
Сам мьютекс (и все остальные примитивы lock-based синхронизации) нужно инициализировать в рантайме.
Следовательно, мы всё равно хоть что-то, да должны сделать на lock-free.
Поскольку это делается единожды, то нет нужды требовать, чтобы это было ещё и wait-free, так что мы вольны использовать спинлок.
Здравствуйте, Кодт, Вы писали:
К>Щас попробую на коленке родить.
К>Сам мьютекс (и все остальные примитивы lock-based синхронизации) нужно инициализировать в рантайме. К>Следовательно, мы всё равно хоть что-то, да должны сделать на lock-free. К>Поскольку это делается единожды, то нет нужды требовать, чтобы это было ещё и wait-free, так что мы вольны использовать спинлок.
...
Так, это уже лучше чем КС с конструктором
пара вопросов:
1. Нужна ли мне КС или инициализировать через do_once саму data_t — какие будут практические рекомендации для выбора ?
2. А вот ты бы оставил "опасный" вариант или делал бы настоящий do_once вроде этого ?
Здравствуйте, bkat, Вы писали:
B>Здравствуйте, Alexander G, Вы писали:
AG>>Если бы всё было так просто, я бы действительно взял КС.
B>Все время забываю, что тебе нужна гарантия в случае если B>кто-то пожелает вызвать getInstance при создании глобальных объектов.
Я действительно хочу много странного, но именно это странным не является. Требование чтобы синглтон работал при конструировании глобальных объектов, в том числе и других синглтонов — стандартное требование. Фактически, самый простой известный синглтон — синглтон Мейерса — решает в первую очередь именно эту задачу — при этом создавая взамен проблемы с многопоточностью и с exception safety.
Если бы существование при инициализации не было бы нужно, я бы просто сделал инициализацию в конструкторе глобального объекта. Можно конечно сделать что-то вроде