特定の数値に達するまでカウンターをインクリメントする必要があります。2 つの並列タスクを使用して数値をインクリメントできます。ロックを使用して数値が最大許容値に達していないかどうかを確認してからインクリメントする代わりに、Interlocked .CompareExchange を次のように使用することを考えました。
public class CompareExchangeStrategy
{
private int _counter = 0;
private int _max;
public CompareExchangeStrategy(int max)
{
_max = max;
}
public void Increment()
{
Task task1 = new Task(new Action(DoWork));
Task task2 = new Task(new Action(DoWork));
task1.Start();
task2.Start();
Task[] tasks = new Task[2] { task1, task2 };
Task.WaitAll(tasks);
}
private void DoWork()
{
while (true)
{
int initial = _counter;
if (initial >= _max)
{
break;
}
int computed = initial + 1;
Interlocked.CompareExchange(ref _counter, computed, initial);
}
}
}
このコードは、ロック アプローチよりも実行に時間がかかります (_max= 1,000,000 の場合)。
public class LockStrategy
{
private int _counter = 0;
private int _max;
public LockStrategy(int max)
{
_max = max;
}
public void Increment()
{
Task task1 = new Task(new Action(DoWork));
Task task2 = new Task(new Action(DoWork));
task1.Start();
task2.Start();
Task[] tasks = new Task[2] { task1, task2 };
Task.WaitAll(tasks);
}
private void DoWork()
{
while (true)
{
lock (_lockObject)
{
if (_counter < _max)
{
_counter++;
}
else
{
break;
}
}
}
}
}
Interlocked.CompareExchange の使用方法に問題がある可能性がありますが、把握できていません。ロック (別名インターロック メソッド) を使用せずに上記のロジックを実行するより良い方法はありますか?
更新
ロック バージョンと同じくらいパフォーマンスの高いバージョンを入手できました (反復 = 1,000,000 で、1,000,000 を超える反復の場合)。
SpinWait spinwait = new SpinWait();
int lock =0;
while(true)
{
if (Interlocked.CompareExchange(ref lock, 1, 0) != 1)
{
if (_counter < _max)
{
_counter++;
Interlocked.Exchange(ref lock, 0);
}
else
{
Interlocked.Exchange(ref lock, 0);
break;
}
}
else
{
spinwait.SpinOnce();
}
}
違いはスピンによって作られます。タスクが最初に変数をインクリメントできない場合、タスク 2 がビジー スピン待機を実行する代わりにさらに進行する機会を提供してスピンします。
lock もほぼ同じことをしていると思います。スピンする戦略を採用して、現在ロックを所有しているスレッドを実行できるようにすることができます。