http://d.hatena.ne.jp/amachang/20080612/1213244820
お気に入りなサイトのIT戦記より
バグっとるよ。
コンパイラは最適化で、tmpを消してpに直接malloc結果を入れる権利があるので、
最初のロック外のif文で!NULLになっても、それは初期化が終わってないかもしれない。
基本的にメモリバリアーを理解してないと、DCLパターンは地雷。
pthread_once()推奨。
(てゆーか、まさにこのためにpthread_once()はある。pthread_mutex()から構築できるようなコンビニエンス関数は規格に入れないぜ。といっていたPOSIXがpthread_onceを入れた理由はここにある)
以下、わかりやすいまとめサイトへの誘導。
バイナリアンなmemologueさんのサイト:
http://d.hatena.ne.jp/yupo5656/20041011/p1
注意: このページではx86用のメモリバリアを
__asm__ __volatile__ ( "" ::: "memory" );
と紹介していますが、これはユニプロセッサ用なので、Core DuoなまかーなAmachanはちゃんとlfence, sfence命令を入れないとやばいです。
Effective C++を書いたScott Meyersさんのサイト
http://www.nwcpp.org/Downloads/2004/DCLP_notes.pdf
最近、社内向けにメモリバリアのドキュメントを書いたばかりなので、反響があればブログにうpする。

光子力バリアー! ランキング!
お気に入りなサイトのIT戦記より
// ここを volatile にする
// (この変数の値はアトミック(つまり、レジスタにだけあってメモリにないということがない変数に)になる)
volatile char* p = NULL;
pthread_mutex_t m;
void* f(void* _p) {
// ロックかからない
if (p == NULL) {
pthread_mutex_lock(&m);
// ここからはクリティカルセクション
// 一個目の初期化時にここでブロックしたスレッドのために
// もう一回 NULL チェック
if (p == NULL) {
// ここではまだ p に代入しない
// 代入したら別スレッドで初期化されていない p が返ってしまう
char* tmp = (char*)malloc(10);
strcpy(tmp, "hoge");
// クリティカルセクションの最後で代入
p = tmp;
}
pthread_mutex_unlock(&m);
}
*(char**)_p = p;
}
バグっとるよ。
コンパイラは最適化で、tmpを消してpに直接malloc結果を入れる権利があるので、
最初のロック外のif文で!NULLになっても、それは初期化が終わってないかもしれない。
基本的にメモリバリアーを理解してないと、DCLパターンは地雷。
pthread_once()推奨。
(てゆーか、まさにこのためにpthread_once()はある。pthread_mutex()から構築できるようなコンビニエンス関数は規格に入れないぜ。といっていたPOSIXがpthread_onceを入れた理由はここにある)
以下、わかりやすいまとめサイトへの誘導。
バイナリアンなmemologueさんのサイト:
http://d.hatena.ne.jp/yupo5656/20041011/p1
注意: このページではx86用のメモリバリアを
__asm__ __volatile__ ( "" ::: "memory" );
と紹介していますが、これはユニプロセッサ用なので、Core DuoなまかーなAmachanはちゃんとlfence, sfence命令を入れないとやばいです。
Effective C++を書いたScott Meyersさんのサイト
http://www.nwcpp.org/Downloads/2004/DCLP_notes.pdf
最近、社内向けにメモリバリアのドキュメントを書いたばかりなので、反響があればブログにうpする。

光子力バリアー! ランキング!
コメント
コメント一覧 (5)
これ、本当なんでしょうか?
Intel 64 and IA-32 Architectures Software Developer's Manual
Volume 3A: System Programming Guide, Part 1
http://www.intel.com/products/processor/manuals/index.htm
の 7.2 MEMORY ORDERING の記述に拠れば、
Core 2 Duoなどの最新プロセッサにおいても R→R, W→W, R→W の
順序が変わらないことは保証されているはずなので、
lfence, sfence命令は不要だと思います。
*fence命令が必要なのはMTRRやPATでキャッシュタイプをいじった場合や
Non-temporal store命令を使用した場合などであり、
C言語で普通に記述されたコードでは考慮しなくてよいのではないでしょうか。
あと、上記の順序が変わらないという話は他プロセッサからみた可視・不可視の話が考慮されていないのでマニュアルの読み方が浅いと思いますよ。
Software Developer's Manual の
7.2.3.2 Neither Loads Nor Stores Are Reordered with Like Operations
7.2.3.3 Stores Are Not Reordered With Earlier Loads
あたりの記述はまさに「他プロセッサからみた可視・不可視の話」ですよね。
これを読んだ結論が、先のコメントです。
読んだ上で言ってるなら僕が間違ってるかもしれないので、ちゃんと調べなおしてから答えますわ。
ただ、現在マニュアルが見れる環境にないのと、ちょっと時間がとれないので、ちょっと遅くなるかもしれないっす。
IA32のマニュアル(手元にあるのは2006年1月版)だと
1. Reads can be carried out speculatively and in any order.
とあるが、Intel64 and IA32マニュアル(ようするに最近のやつ)だと
・Reads are not reordered with other reads.
とあるので、yamasaさんが正しそうな感じ。
しかし、時代と共にメモリオーダは緩くなっていく一方だと思っていたのに、
IntelはP6, Pentium4にさかのぼってメモリオーダ規則を厳しくしたのね。
#マニュアルが後から直せるということは、実装とマニュアルが合ってなかったんだろうけど。
眠いので嘘を言っているかもしれないので、明日、再調査しよう。