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