2

Так что между тем мы знаем, что двойной контроль-блокировка как есть, не работает на C++, по крайней мере, не в переносном режиме.Тройная проверка блокировки?

Я только что понял, что у меня хрупкая реализация в ленивом квадранте, который я использую для трассировщика луча местности. Поэтому я попытался найти способ по-прежнему использовать ленивую инициализацию безопасным образом, так как я не хотел бы увеличивать объем памяти и переупорядочивать большие части реализованных алгоритмов.

Этот обход вдохновлен рисунком на странице 12 C++ and the Perils of Double-Checked Locking, но пытается сделать это дешевле:

(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 будет также служить в качестве твердой точки последовательности, где компиляторы и процессоры выиграли» t разрешить переупорядочивать операции по ним.

Какие проблемы вы видите?

редактировать: Version 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; 
       } 
      } 
     } 
    } 
} 

редактировать: Version 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; 
      } 
     } 
    } 
} 

ответ

3

Похоже, что ваш рисунок неправильный. Рассмотрим случай, когда поток # 1 выполняется до первого #pragma flush. Затем управление переключается на нить # 2, которая продолжается и создает c, элемент управления берется обратно перед вторым #pragma flush. Теперь первый поток просыпается и создает ребенка заново.

Редактировать: извините, не так: он не сможет взять замок.

Редактировать 2: нет, все еще правильно, потому что значение не будет промыто в резьбе # 1

+0

Я добавил еще одну ревизию. Хотя я должен признать, что этот день был долгим, и я медленно выхожу из мозгов. –

+0

Я думаю, что нам все еще нужен барьер между 'create (c);' и 'childCreated [c] = true;', в противном случае они могут быть переупорядочены (и поток # 3 может начать использовать 'c', который все еще не создан) , – Vlad

+0

после добавления третьей ревизии, которая является только дважды проверенным шаблоном, я понял именно это :) Но когда я вижу свою третью ревизию, это выглядит слишком тривиально, чтобы быть верным. И пить больше кофе больше не является вариантом, так как я уже насыщен этим:/ –

Смежные вопросы