4

我需要增加一个计数器,直到它达到一个特定的数字。我可以使用两个并行任务来增加数字。我没有使用锁来检查数字是否未达到最大允许值然后递增,而是以下列方式使用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 提供进一步进行的机会,而不是执行繁忙的旋转等待。

我怀疑锁的作用几乎相同,它可以采用一种策略来旋转并允许当前拥有锁的线程执行。

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 回答