Тройная проверка блокировки?
Таким образом, в то же время мы знаем, что двойная проверка блокировки как есть не работает в C++, по крайней мере, в переносимой форме.
Я только что понял, что у меня хрупкая реализация в ленивом дереве, которое я использую для трассировки лучей местности. Поэтому я попытался найти способ все еще использовать ленивую инициализацию безопасным способом, поскольку я не хотел бы в четыре раза использовать память и переупорядочивать большие части реализованных алгоритмов.
Этот обход основан на паттерне C++ на странице 12 и опасностях двойной проверки блокировки, но он пытается сделать это дешевле:
(pseudo code!)
struct Foo {
bool childCreated[4];
Mutex mutex[4];
Foo child[4];
void traverse (...) {
...
if (!childCreated[c]) {
// get updated view
#pragma flush childCreated[c]
if (!childCreated[c]) {
ScopedLock sl (mutex[c]);
if (!childCreated[c]) {
create (c);
#pragma flush childCreated[c]
childCreated[c] = true;
}
}
}
}
}
Предполагается, что #pragma flush
будет также служить жесткой последовательностью, где компиляторам и процессорам не будет разрешено переупорядочивать операции между ними.
Какие проблемы вы видите?
редактировать: версия 2, пытаясь учесть ответ Владис (ввести третий флеш):
(pseudo code!)
struct Foo {
bool childCreated[4];
Mutex mutex[4];
Foo child[4];
void traverse (...) {
...
if (!childCreated[c]) {
// get updated view
#pragma flush childCreated[c]
if (!childCreated[c]) {
ScopedLock sl (mutex[c]);
#pragma flush childCreated[c]
if (!childCreated[c]) {
create (c);
#pragma flush childCreated[c]
childCreated[c] = true;
}
}
}
}
}
edit: Версия 3, я как-то нахожу это довольно эквивалентным Версии 2, потому что я не использую сам дочерний элемент, но примитивный флаг для проверки на допустимость, в основном полагаясь на барьер памяти между созданием дочернего элемента и записью в этот флаг.
(pseudo code!)
struct Foo {
bool childCreated[4];
Mutex mutex[4];
Foo child[4];
void traverse (...) {
...
if (!childCreated[c]) {
ScopedLock sl (mutex[c]);
#pragma flush childCreated[c]
if (!childCreated[c]) {
create (c);
#pragma flush childCreated[c]
childCreated[c] = true;
}
}
}
}
1 ответ
Кажется, что ваш шаблон не является правильным. Рассмотрим случай, когда поток #1 выполняется до после первого #pragma flush
, Затем управление переключается на поток № 2, который продолжается и создает c
, контроль забирается незадолго до второго #pragma flush
, Теперь первая нить просыпается и заново создает ребенка.
Изменить: извините, неправильно: он не сможет взять блокировку.
Изменить 2: нет, все еще правильно, потому что значение не будет сброшено в потоке #1