Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9715

配列の要素に対するInterlockedメソッドはスレッドセーフか?

$
0
0
マルチスレッドで配列の要素がNULLだったら値を入れるみたいな事がやりたかった。 アクセス頻度が高くlockステートメントを使うと遅くて実用に耐えないので Interlocked.CompareExchangeでやりたいけど、大丈夫なんかいなというお話。 ソース的には雰囲気こんな感じ。 var array = new Class[N]; // (中略) Interlocked.CompareExchange(ref array[i], new Class(), null); 結論としては「きっと大丈夫、たぶん」 まず配列の要素に対するrefはOK。 よって参照の割り当てはアトミックとされているので上記コードは理論上スレッドセーフとして成り立つはず。 ただやっぱりあまり例のない使い方で少し不安なのでInterlockedの「分割不可能な操作」の正体について調べてみた。 ドキュメントには分割不可能な操作としか書かれていないので.NETのソースコードを追っていくと どんどんネイティブな所に連れていかれ、最終的にはCPUレベルで実現しているという事がわかりました。 なので実装はCPUアーキテクチャによって変わりますが、x64の場合はこんな感じ。 // x64 lock cmpxchg qword ptr [mem64],rdx 念のため検証コードも回してみましたが問題なさそう。 const int N = 1000; var array1 = new object[100000]; var array2 = new object[100000]; var counter1 = 0; var counter2 = 0; // 非スレッドセーフ Parallel.For(0, N, _ => { for (var i = 0; i < array1.Length; i++) { if (array1[i] == null) { array1[i] = new object(); Interlocked.Increment(ref counter1); } } }); // スレッドセーフ Parallel.For(0, N, _ => { for (var i = 0; i < array2.Length; i++) { if (Interlocked.CompareExchange(ref array2[i], new object(), null) == null) { Interlocked.Increment(ref counter2); } } }); Console.WriteLine($"{counter1}, {counter2}"); 結果 173847, 100000 余談ですが、.NETのInterlocked回りのコードはコメントに色々書かれていて見ていて楽しいです。 参考 C# Reference - ref Keyword C# Reference - .NET gabage collection SourceCode - Interlocked.CompareExchnage (.NET Framework) SourceCode - Interlocked.CompareExchange (.NET Core) Windows API - InterlockedCompareExchange

Viewing all articles
Browse latest Browse all 9715

Trending Articles