我一直在尝试多线程和并行处理,并且我需要一个计数器来对处理速度进行一些基本的计数和统计分析。为了避免并发使用类的问题,我在类中的私有变量上使用了lock语句:
private object mutex = new object();
public void Count(int amount)
{
lock(mutex)
{
done += amount;
}
}
但是我想知道...锁定变量有多昂贵?对性能的负面影响是什么?
我一直在尝试多线程和并行处理,并且我需要一个计数器来对处理速度进行一些基本的计数和统计分析。为了避免并发使用类的问题,我在类中的私有变量上使用了lock语句:
private object mutex = new object();
public void Count(int amount)
{
lock(mutex)
{
done += amount;
}
}
但是我想知道...锁定变量有多昂贵?对性能的负面影响是什么?
Answers:
lock
与使用变量的成本相比,您不必担心使用任何其他成本。
从技术上来说,这是无法量化的,它在很大程度上取决于CPU内存回写缓冲区的状态以及必须丢弃并重新读取预取器收集的多少数据。两者都是不确定的。我使用150个CPU周期作为信封的近似值,以避免出现严重的失望。
实际的答案是,它是waaaay比的时候,你会燃烧在调试代码时,你认为你可以跳过锁量更便宜。
要获得一个硬数字,您必须进行测量。Visual Studio具有一个精巧的并发分析器作为扩展。
我想介绍一些我对通用同步原语感兴趣的文章,并且它们将根据不同的场景和线程数来研究Monitor,C#锁语句的行为,属性和成本。对于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
噢亲爱的!
似乎这里标记为答案的正确答案本质上是不正确的!我想请答案的作者仔细阅读链接的文章。文章
2003年文章的作者仅在双核计算机上进行测量,在第一个测量案例中,他仅使用单线程测量了锁定,因此每次锁定访问的结果约为50ns。
它并没有说明并发环境中的锁定。因此,我们必须继续阅读该文章,下半年,作者正在测量具有两个和三个线程的锁定方案,该方案已接近当今处理器的并发级别。
因此,作者说,双核上有两个线程,锁的成本为120ns,而有3个线程,锁的成本为180ns。因此,这似乎显然取决于并发访问锁的线程数。
因此很简单,除非锁是无用的单线程,否则它不是50 ns。
需要考虑的另一个问题是,它以平均时间来衡量!
如果要测量迭代时间,则仅在大多数情况下,这就是1ms到20ms之间的时间间隔,只是因为大多数线程速度很快,但是很少有线程会等待处理器时间,甚至会导致毫秒级的延迟。
对于任何需要高吞吐量,低延迟的应用程序来说,这都是一个坏消息。
最后要考虑的问题是,锁内部的操作可能会变慢,而且通常是这种情况。在锁内执行代码块的时间越长,争用就越多,延迟就越长。
请考虑,自2003年以来已经过去了十多年,专门设计用于完全并发运行的处理器只有几代了,而锁定会严重损害它们的性能。
这不能回答您有关性能的查询,但是我可以说.NET Framework确实提供了一种Interlocked.Add
方法,该方法将允许您将您amount
的done
成员添加到您的成员中,而无需手动锁定另一个对象。
lock
(Monitor.Enter / Exit)非常便宜,比Waithandle或Mutex等替代方法便宜。
但是,如果速度变慢(有点)怎么办,您宁愿拥有一个结果不正确的快速程序吗?
与不带锁的替代方案相比,紧密循环中的锁的成本很高。您可以承受多次循环,但仍然比锁更有效率。这就是为什么无锁队列如此高效的原因。
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