Комментарии 21
Сочетание Interlocked* с *CriticalSection особенно насмешило.
В приведенном примере нельзя обойтись без переменной temp_a. А вот если записать так, то можно:
a=max(0, calculate_nominal_a());
У меня вопрос: а зачем используется InterlockedCompareExchangeRelease вместо InterlockedCompareExchange? Как-то об этом в статье не сказано ни слова.
a=max(0, calculate_nominal_a());
У меня вопрос: а зачем используется InterlockedCompareExchangeRelease вместо InterlockedCompareExchange? Как-то об этом в статье не сказано ни слова.
а зачем используется InterlockedCompareExchangeRelease вместо InterlockedCompareExchange?
Записьb
в память необходимо оформить как операцию Release; тогда все предыдущие операции вынуждены отразиться в памяти до того, как другие процессоры увидят новое значениеb
.
InterlockedCompareExchange применяет полноценный барьер памяти, а InterlockedCompareExchangeRelease только вторую половину, на выходе — так она чуток эффективнее, но имеет ограниченное применение.
Вот ссылки на русском:
http://www.rsdn.ru/forum/cpp/3734032.1.aspx
http://www.data-race.com/2010/10/29/memory-barriers/
Вот ссылки на русском:
http://www.rsdn.ru/forum/cpp/3734032.1.aspx
http://www.data-race.com/2010/10/29/memory-barriers/
Интересно есть ли какой-нибудь шаблонный код для потоко-безопасной ленивой инициализации в C++. В C# есть Lazy для такого рода задач
Начиная с Windows Vista, есть функции InitOnce: msdn.microsoft.com/en-us/library/aa363808
В с++0х пофиксили нереентерабельность:
«If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.»
но осталось неопределённое поведение для рекурсивной инициализации:
«If control re-enters the declaration recursively while the variable is being initialized, the behavior is undefined.»
«If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization.»
но осталось неопределённое поведение для рекурсивной инициализации:
«If control re-enters the declaration recursively while the variable is being initialized, the behavior is undefined.»
А вот умничка gcc для такого кода
генерирует такой псевдокод (gcc/cp/decl.c):
class Something
{
int v;
public:
Something() throw();
int f();
};
int f()
{
static Something s;
return s.f();
}
генерирует такой псевдокод (gcc/cp/decl.c):
/* Emit code to perform this initialization but once. This code
looks like:
static guard;
if (!guard.first_byte) {
if (__cxa_guard_acquire (&guard)) {
bool flag = false;
try {
// Do initialization.
flag = true; __cxa_guard_release (&guard);
// Register variable for destruction at end of program.
} catch {
if (!flag) __cxa_guard_abort (&guard);
}
}
превращающийся в такой ассемблерный исходник:
f():
.LFB0:
.cfi_startproc
.cfi_personality 0x3,__gxx_personality_v0
pushq %rbp
.cfi_def_cfa_offset 16
.cfi_offset 6, -16
movq %rsp, %rbp
.cfi_def_cfa_register 6
movl guard variable for f()::s, %eax
movzbl (%rax), %eax
testb %al, %al
jne .L2
movl guard variable for f()::s, %edi
call __cxa_guard_acquire
testl %eax, %eax
setne %al
testb %al, %al
je .L2
movl f()::s, %edi
call Something::Something()
movl guard variable for f()::s, %edi
call __cxa_guard_release
.L2:
movl f()::s, %edi
call Something::f()
leave
.cfi_def_cfa 7, 8
ret
.cfi_endproc
А __cxa_guard_acquire/__cxa_guard_release в правильно сконфигурированном libstdc++ использует мьютексы (libstdc++-v3/libsupc++/guard.cc).
gcc, бесспорно, умничка, но, на мой взгляд, не каждый рискнёт писать код, полагающийся на особенности конкретной версии конкретного компилятора.
Ну, InterlockedCompareExchangeRelease это вообще особенности одной операционной системы на одной архитектуре (;
Ну. Такую функцию очень просто написать для любой архитектуры. Как бы, алгоритм Лампорта достаточно универсален.
Алгоритм Лампорта (полагаю, имеется в виду Bakery) это как раз «замочная» синхронизация, спинлок не требующий специальной поддержки со стороны процессора.
Собственно, InterlockedCompareExchangeXxx и так реализованы на целом зоопарке архитектур.
(Напомню, NT поддерживала или поддерживает x86, x64, ia64, Alpha, MIPS, PowerPC, и ARM.)
(Напомню, NT поддерживала или поддерживает x86, x64, ia64, Alpha, MIPS, PowerPC, и ARM.)
Неблокирующая инициализация звучит как-то правильнее, чем беззамочная, да и понятнее, а то, пока не начал читать, думал, что это что-то диковинное.
А вообще, метод предложенный какой-то совсем странный. Мало того, что работает только с примитивными типами данных, да ещё и требует, чтобы
calculate_nominal
-функции были чистыми. А если они чистые, то вообще какой смысл в lazy-инициализации? Посчитать заранее константы, и наступит счастье, в том числе и по эффективности.Они не должны быть чистыми в математическом смысле; нужно лишь, чтоб их значение не менялось за время выполнения программы.
Предположим, они читают файл конфигурации. Предположим, с удалённого сервера. Согласны, что в этом случае их ленивый вызов оправдан?
Предположим, они читают файл конфигурации. Предположим, с удалённого сервера. Согласны, что в этом случае их ленивый вызов оправдан?
В этом случае согласен. Но с такими функциями предлагаемый метод работать просто не будет. Потому что, эти функции, скорее всего, будут возвращать некие сложные объекты, в которых, вполне возможно, будут перегружены операторы =, и параллельное присваивание из временных объектов в static-ческие, может не сработать.
Тут уж лучше 5 копеек потерять на входе в критическую секцию для инициализации при первом вызове функции со static-переменными, чем потом гоняться за багами, вызыванными нереентерабельностью какой-нибудь хитрой.
Тут уж лучше 5 копеек потерять на входе в критическую секцию для инициализации при первом вызове функции со static-переменными, чем потом гоняться за багами, вызыванными нереентерабельностью какой-нибудь хитрой.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий
Потоко-безопасная ленивая инициализация в C++