锁定声明有多贵?

Kee*_*ker 101 .net c# parallel-processing multithreading locking

我一直在尝试多线程和并行处理,我需要一个计数器来对处理速度进行一些基本的计数和统计分析.为了避免同时使用我的类的问题,我在我的类中的私有变量上使用了一个lock语句:

private object mutex = new object();

public void Count(int amount)
{
 lock(mutex)
 {
  done += amount;
 }
}
Run Code Online (Sandbox Code Playgroud)

但我想知道......锁定变量有多贵?对性能有负面影响?

Jak*_*son 74

这是一篇涉及成本的文章.简短回答是50ns.

  • 简短的答案:如果其他线程持有锁定,等待50ns +等待的时间. (33认同)
  • 一些上下文:在3Ghz x86上划分两个数字需要大约10ns _(不包括获取/解码指令所需的时间)_; 并将(非缓存)内存中的单个变量加载到寄存器中需要大约40ns.所以50ns是疯狂的,_blindingly_快 - 你不应该担心使用`lock`的成本比你担心使用变量的成本更多. (12认同)
  • 进入和离开锁定的线程越多,它就越昂贵.成本随线程数呈指数级增长 (3认同)
  • 此外,当提出这个问题时,那篇文章已经过时了. (3认同)
  • 真的很棒的指标,"几乎没有成本",更不用说不正确了.你们没有考虑到它只是短暂而快速的,只有在没有争用的情况下,一个线程.在这种情况下,你根本不需要锁定.第二个问题,锁不是锁,而是混合锁,它在CLR内部检测到任何人都没有根据原子操作持有锁,在这种情况下,它避免了对操作系统核心的调用,这是不同的环测量试验.如果不采取锁定,则测量为25ns至50ns的实际应用级别互锁指令代码 (3认同)

Han*_*ant 48

技术问题是这是不可能量化的,它在很大程度上取决于CPU内存回写缓冲区的状态以及预取器收集的数据必须被丢弃和重新读取的数量.哪些都是非常不确定的.我使用150个CPU周期作为包络近似,避免了重大的失望.

实际的答案是,它是waaaay比的时候,你会燃烧在调试代码时,你认为你可以跳过锁量更便宜.

为了得到一个难以衡量的数字.Visual Studio有一个灵活的并发分析器作为扩展.

  • *"认为你可以跳过锁定"*......我认为这是很多人在阅读这个问题时所处的位置...... (7认同)
  • 其实不是,是可以量化和衡量的。这并不像在代码周围编写这些锁那么容易,然后声明它只是 50ns,这是一个以单线程访问锁为衡量标准的神话。 (2认同)

ipa*_*vlu 27

进一步阅读:

我想介绍一些我对通用同步原语感兴趣的文章,他们正在深入研究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年开始已经过去了十多年,那几代处理器专门设计为完全同时运行并且锁定会严重损害其性能.

  • 澄清一下,这篇文章并不是说锁性能会随着应用程序中线程的数量而降低;性能会随着争用锁的线程数量而下降。(这是暗示,但没有明确说明,在上面的答案中。) (2认同)

Ada*_*ras 20

这不会回答您关于性能的查询,但我可以说.NET Framework确实提供了一种Interlocked.Add方法,允许您将您amountdone成员添加到您的成员而无需手动锁定另一个对象.


Hen*_*man 10

lock (Monitor.Enter/Exit)非常便宜,比Waithandle或Mutex等替代品便宜.

但是,如果它(有点)缓慢,你宁愿有一个快速的程序,结果不正确吗?

  • 哈哈......我本来是为了快节目和好成绩. (5认同)

Joh*_*son 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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

输出:

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
Run Code Online (Sandbox Code Playgroud)

  • 这可能是一个糟糕的例子,因为除了单个变量赋值和锁定至少2个函数调用之外,你的循环实际上什么都不做.此外,你获得的每锁20ns并不是那么糟糕. (4认同)

Kei*_*thS 5

有几种不同的方法来定义“成本”。获取和释放锁存在实际的开销;正如杰克(Jake)所写,除非执行此操作数百万次,否则这可以忽略不计。

与此相关的是对执行流程的影响。该代码一次只能由一个线程输入。如果您有5个线程定期执行此操作,则其中4个线程最终将等待释放锁,然后成为释放该锁之后计划输入该代码段的第一个线程。因此,您的算法将遭受重大损失。这多少取决于算法以及调用该操作的频率。在不引入竞争条件的情况下,您无法真正避免它,但是可以通过减少对锁定代码的调用次数来改善它。