そのため、現時点では、ダブルチェック ロックをそのまま使用しても C++ では機能しないことがわかっています。少なくとも、移植性のある方法では機能しません。
テレイン レイ トレーサーに使用する遅延四分木に脆弱な実装があることに気付きました。そこで、メモリ使用量を 4 倍にしたり、実装されたアルゴリズムの大部分を並べ替えたりしたくないので、安全な方法で遅延初期化を引き続き使用する方法を見つけようとしました。
このトラバーサルは、 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、Vlads の回答を考慮しようとしています (3 番目のフラッシュを導入):
(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;
}
}
}
}
}
編集:バージョン 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;
}
}
}
}