Как стать автором
Обновить

Комментарии 21

Сочетание Interlocked* с *CriticalSection особенно насмешило.
В приведенном примере нельзя обойтись без переменной temp_a. А вот если записать так, то можно:
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/
Интересно есть ли какой-нибудь шаблонный код для потоко-безопасной ленивой инициализации в C++. В C# есть Lazy для такого рода задач
В с++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.»
Всё же не «пофиксили», а «собираются пофиксить». Стандарт всё ещё не принят.
Осталось поправить опечатки и проголосовать. Формальность.
А вот умничка gcc для такого кода

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) это как раз «замочная» синхронизация, спинлок не требующий специальной поддержки со стороны процессора.
Эмс. Ну и что? Речь же о том, что всегда есть способ реализовать interlocked-функцию. Ну, пусть будет не самое эффективное решение… Хотя, на самом деле, вот даже и не факт, что это будет менее эффективно… cmpxchg — тоже не самая быстрая операция. Тут уже тестировать надо производительность.
Собственно, InterlockedCompareExchangeXxx и так реализованы на целом зоопарке архитектур.
(Напомню, NT поддерживала или поддерживает x86, x64, ia64, Alpha, MIPS, PowerPC, и ARM.)
Неблокирующая инициализация звучит как-то правильнее, чем беззамочная, да и понятнее, а то, пока не начал читать, думал, что это что-то диковинное.
А вообще, метод предложенный какой-то совсем странный. Мало того, что работает только с примитивными типами данных, да ещё и требует, чтобы calculate_nominal-функции были чистыми. А если они чистые, то вообще какой смысл в lazy-инициализации? Посчитать заранее константы, и наступит счастье, в том числе и по эффективности.
Они не должны быть чистыми в математическом смысле; нужно лишь, чтоб их значение не менялось за время выполнения программы.
Предположим, они читают файл конфигурации. Предположим, с удалённого сервера. Согласны, что в этом случае их ленивый вызов оправдан?
В этом случае согласен. Но с такими функциями предлагаемый метод работать просто не будет. Потому что, эти функции, скорее всего, будут возвращать некие сложные объекты, в которых, вполне возможно, будут перегружены операторы =, и параллельное присваивание из временных объектов в static-ческие, может не сработать.

Тут уж лучше 5 копеек потерять на входе в критическую секцию для инициализации при первом вызове функции со static-переменными, чем потом гоняться за багами, вызыванными нереентерабельностью какой-нибудь хитрой.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории