2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Posted at

マルチスレッドで配列の要素が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回りのコードはコメントに色々書かれていて見ていて楽しいです。

参考

2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?