Тройная проверка блокировки?

Таким образом, в то же время мы знаем, что двойная проверка блокировки как есть не работает в 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

Другие вопросы по тегам