我需要增加一个计数器,直到达到一个特定的数字。我可以使用两个并行任务来增加数字。我没有使用锁来检查数字是否未达到允许的最大值然后递增,而是考虑使用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的方式可能有问题,但我一直无法理解。有没有更好的方法来执行上述逻辑而不使用锁(又名Interlocked方法)?
<小时/> 的更新
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提供进一步推进的机会,而不是执行繁忙的旋转等待。
我怀疑锁也是如此,它可以采用一种策略来旋转并允许当前拥有锁的线程执行。
答案 0 :(得分: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
没有任何问题,实际上它在内部进行了非常优化。无锁不会自动与“最快”或“最简单”相同。
答案 1 :(得分:-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;
}
}
}