Using Interlocked.CompareExchange to increment a counter until a value

会有一股神秘感。 提交于 2019-12-05 11:55:35
Marc Gravell

The problem here is that you are actually doing a lot more work in the Interlocked version - by which I mean more iterations. This is because a lot of the time the CompareExchange isn't doing anything, because the value was changed by the other thread. You can see this by adding a total to each loop:

    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);

(note I also added a VolatileRead to ensure _counter isn't held in a register)

I get much more than iterations (via total) that you might expect here. The point is that when using Interlocked in this way, you need to add a strategy for what happens if the value changed, i.e. a retry strategy.

For example, a crude retry strategy might be:

    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++;
    }

which is to say: keep retrying until you make it work - any "doing" code would only happen after that check (where the total++ line is currently). This, however, makes the code more expensive.

If lock is cheaper: use lock. There's nothing wrong with lock, and indeed it is very optimized internally. Lock-free is not automatically the same as "fastest" or indeed "simplest".

I've managed to achieve almost the same performance as lockstrategy using the following code:

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;
            }
        }
    }
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!