我一直在尝试多线程和并行处理,我需要一个计数器来对处理速度进行一些基本的计数和统计分析。为了避免同时使用我的类的问题,我在我的类中使用了一个私有变量的锁语句:
private object mutex = new object();
public void Count(int amount)
{
lock(mutex)
{
done += amount;
}
}
但我想知道......锁定变量有多贵?对绩效有负面影响?
答案 0 :(得分:71)
这是费用的an article。简短回答是50ns。
答案 1 :(得分:47)
技术问题是,这是不可能量化的,它在很大程度上取决于CPU内存回写缓冲区的状态以及预取器收集的数据必须被丢弃和重新读取的数量。哪些都是非常不确定的。我使用150个CPU周期作为包络近似,避免了重大的失望。
实际的答案是,当您认为可以跳过锁定时, waaaay 比调试代码时所消耗的时间更便宜。
要得到一个难以衡量的数字。 Visual Studio有一个灵活的concurrency analyzer可用作扩展名。
答案 2 :(得分:25)
我想提供一些对我的一些文章,这些文章对一般同步原语感兴趣,他们正在深入研究Monitor,C#lock语句行为,属性和成本,具体取决于不同的场景和线程数。它特别感兴趣的是CPU浪费和吞吐量周期,以了解在多种情况下可以推进多少工作:
https://www.codeproject.com/Articles/1236238/Unified-Concurrency-I-Introduction https://www.codeproject.com/Articles/1237518/Unified-Concurrency-II-benchmarking-methodologies https://www.codeproject.com/Articles/1242156/Unified-Concurrency-III-cross-benchmarking
哦亲爱的!
这里似乎正确答案标记为答案本质上是不正确的!我想恭敬地向作者提出答案,将链接的文章读到最后。 article
2003年article文章的作者仅在双核机器上进行测量,在第一个测量案例中,他仅使用单线测量锁定,结果约为50ns每次锁定访问。
它没有说明并发环境中的锁定。 所以我们必须继续阅读这篇文章,在下半部分,作者用两个和三个线程测量锁定场景,这更接近当今处理器的并发级别。
所以作者说,在双核上使用两个线程,锁定成本为120ns,而使用3个线程则成为180ns。所以它似乎明显依赖于同时访问锁的线程数。
所以它很简单,它不是50 ns,除非它是单个线程,锁无效。
需要考虑的另一个问题是它被测量为平均时间!
如果测量迭代的时间,甚至会在1ms到20ms之间,只是因为大多数是快速的,但是很少有线程会等待处理器时间并且甚至会产生几毫秒的延迟。
对于任何需要高吞吐量,低延迟的应用程序来说,这都是坏消息。
最后一个需要考虑的问题是锁内部的操作可能会更慢,而且经常是这种情况。 锁内部执行的代码块越长,争用越多,延迟时间越高。
请注意,从2003年开始已经过去了十多年,那几代处理器专门设计为完全同时运行并且锁定会严重损害其性能。
答案 3 :(得分:20)
这不会回答您关于性能的查询,但我可以说.NET Framework确实提供了Interlocked.Add
方法,允许您将amount
添加到done
没有手动锁定另一个对象的成员。
答案 4 :(得分:10)
lock
(Monitor.Enter / Exit)非常便宜,比Waithandle或Mutex等替代品便宜。
但是,如果它(有点)缓慢,你宁愿有一个快速的程序,结果不正确吗?
答案 5 :(得分:6)
与没有锁定的替代方案相比,在紧密循环中锁定的成本是巨大的。您可以承受多次循环,并且仍然比锁更有效。这就是锁定免费队列如此高效的原因。
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace LockPerformanceConsoleApplication
{
class Program
{
static void Main(string[] args)
{
var stopwatch = new Stopwatch();
const int LoopCount = (int) (100 * 1e6);
int counter = 0;
for (int repetition = 0; repetition < 5; repetition++)
{
stopwatch.Reset();
stopwatch.Start();
for (int i = 0; i < LoopCount; i++)
lock (stopwatch)
counter = i;
stopwatch.Stop();
Console.WriteLine("With lock: {0}", stopwatch.ElapsedMilliseconds);
stopwatch.Reset();
stopwatch.Start();
for (int i = 0; i < LoopCount; i++)
counter = i;
stopwatch.Stop();
Console.WriteLine("Without lock: {0}", stopwatch.ElapsedMilliseconds);
}
Console.ReadKey();
}
}
}
输出:
With lock: 2013
Without lock: 211
With lock: 2002
Without lock: 210
With lock: 1989
Without lock: 210
With lock: 1987
Without lock: 207
With lock: 1988
Without lock: 208
答案 6 :(得分:4)
有几种不同的方法来定义“成本”。获得和释放锁的实际开销;正如杰克所写的那样,除非这项行动被执行了数百万次,否则这种情况可以忽略不计。
更重要的是它对执行流程的影响。此代码一次只能由一个线程输入。如果您有5个线程定期执行此操作,其中4个将最终等待释放锁定,然后成为第一个计划在该锁定释放后输入该代码的线程。因此,您的算法将遭受重大损失。多少取决于算法以及调用操作的频率。如果不引入竞争条件,您无法真正避免它,但您可以通过最小化对锁定代码的调用次数来改善它。