4

特定の数値に達するまでカウンターをインクリメントする必要があります。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 もほぼ同じことをしていると思います。スピンする戦略を採用して、現在ロックを所有しているスレッドを実行できるようにすることができます。

4

2 に答える 2

4

ここでの問題は、実際にはInterlockedバージョンでより多くの作業を行っていることです。つまり、より多くの反復を意味します。これは、他のスレッドによって値が変更されたために、多くの場合、CompareExchange が何もしていないためです。これは、各ループに合計を追加することで確認できます。

    int total = 0;
    while (true)
    {
        int initial = Thread.VolatileRead(ref _counter);
        if (initial >= _max)
        {
            break;
        }
        int computed = initial + 1;
        Interlocked.CompareExchange(ref _counter, computed, initial);
        total++;
    }
    Console.WriteLine(total);

(レジスターに保持されないVolatileReadようにするために、も追加したことに注意してください)_counter

私はあなたがここで期待するかもしれない反復(経由)よりもはるかに多くを得る。total重要なのは、このように使用Interlockedする場合、値が変更された場合に何が起こるかについての戦略、つまり再試行戦略を追加する必要があるということです。

たとえば、大まかな再試行戦略は次のようになります。

    while (true)
    {
        int initial = Thread.VolatileRead(ref _counter);
        if (initial >= _max)
        {
            break;
        }
        int computed = initial + 1;
        if (Interlocked.CompareExchange(ref _counter, computed, initial)
                          != initial) continue;
        total++;
    }

つまり、機能するまで再試行を続けます。「実行中」のコードは、そのチェック(現在の行がある場所)の後でのみ発生します。total++ただし、これによりコードがより高価になります。

lock安い場合:を使用しますlock。には何の問題もありませんlock。実際、内部で非常に最適化されています。ロックフリーは、自動的に「最速」または「最も単純」と同じではありません。

于 2013-03-19T09:42:36.217 に答える
-1

次のコードを使用して、lockstrategy とほぼ同じパフォーマンスを達成することができました。

public class CompareExchangeStrategy {
        volatile 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) {
                if(Interlocked.Add(ref _counter, 1) >= _max)
                    break;
            }
        }
    }
于 2013-03-19T09:49:10.517 に答える