使用Interlocked.CompareExchange递增计数器,直到值 [英] Using Interlocked.CompareExchange to increment a counter until a value
问题描述
我需要,直到达到一个特定的次数递增计数器。我可以使用两个并行任务递增的数目。而不是使用一个锁,检查数量并没有达到最大允许值,然后递增的,我想用的以下方式互锁 .CompareExchange:
I need to increment a counter until it reaches a particular number. I can use two parallel task to increment the number. Instead of using a lock to check if the number has not reach the maximum allowed value and then incrementing, I thought using Interlocked.CompareExchange in the following manner:
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),比锁的方法:
This code is taking more to execute (for _max= 1,000,000) than the lock approach:
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的方式有问题,但我一直无法弄清楚。有没有办法不使用锁(又名互锁方法)执行上述逻辑更好的办法?
There might be a problem with the way I am using Interlocked.CompareExchange but I have not been able to figure out. Is there a better way to perform the above logic without using lock (aka Interlocked methods)?
的更新
结果
我能来与它执行为有锁版一样好一个版本(迭代= 1,000,000,更好的为> 1,000,000迭代)。
Update
I was able to come with a version which performs as good as the lock version (for iterations = 1,000,000 and better for > 1,000,000 iterations).
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契机,进一步进步,而不是执行一个繁忙的自旋等待。
The difference is made by the spin. If the task is unable to increment the variable on first go it spins providing an opportunity for task 2 to progress further instead of performing a busy spin wait.
我怀疑锁定漂亮很多不一样的,它可能采用的策略旋转,并允许线程当前持有锁来执行。
I suspect lock pretty much does the same, it could employ a strategy to spin and allow the thread currently owning the lock to execute.
推荐答案
问题这里要说的是,你实际上是做了很多工作互锁
版本 - 我指的是更多的迭代。这是因为, 很多的时间的 CompareExchange
的没有做任何事情的,因为该值是由其他线程改变。您可以通过共增加每个回路看到这一点:
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);
(注意我还添加了 VolatileRead
来确保 _counter
不是保存在寄存器中)
(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".
这篇关于使用Interlocked.CompareExchange递增计数器,直到值的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!