2014-04-02 58 views
8

更新 - 我发现lock()吃了CPU周期的原因就像疯了一样,我在原始问题后添加了这些信息。这一切都被证明是文本的墙这样:c#锁定语句性能

TL; DR C#的内置lock()机制,在某些情况下,使用的CPU时间不寻常的数量,如果您的系统与一个高位运行分辨率系统定时器

原题:

我有多个线程访问资源的应用程序。资源是连接到USB的设备。它是一个简单的命令/响应接口,我使用一个小的lock()块来确保发送命令的线程也获得响应。 我的实现使用了锁(OBJ)关键词:

lock (threadLock) 
{ 
    WriteLine(commandString); 
    rawResponse = ReadLine(); 
} 

当我访问此从3个线程尽可能快地(在紧凑循环)CPU使用率是高端计算机上约24%。由于USB端口的特性,每秒只能执行大约1000次命令/响应操作。 然后,我实现了这里所描述SimpleExclusiveLock锁定机构,现在的代码类似于此(一些try/catch语句东东以解除锁定在I/O异常的情况下被删除):

Lock.Enter(); 
WriteLine(commandString); 
rawResponse = ReadLine(); 
Lock.Exit(); 

使用该实施使用相同的3个线程测试程序,CPU使用率下降到< 1%,同时仍然每秒获得1000次命令/响应操作。

问题是:在这种情况下,使用内置lock()关键字的问题是什么?

我不小心偶然发现lock()机制的开销非常高的情况?进入关键部分的线程只会持续约1 ms的锁定时间。

更新: lock()像疯了似的吃CPU的原因是,一些应用程序增加了计时器的分辨率为WINMM.DLL使用timeBeginPeriod()整个系统。在我的案件的罪魁祸首是谷歌Chrome和SQL服务器 - 他们要求使用1毫秒的系统计时器分辨率:

[DllImport("winmm.dll", EntryPoint = "timeBeginPeriod", SetLastError = true)] 
private static extern uint TimeBeginPeriod(uint uMilliseconds); 

我发现了这一点,通过使用powercfg工具:

powercfg -energy duration 5 

由于某种内置lock()声明中的设计缺陷,这种增加的定时器分辨率会像疯狂一样吃CPU(至少在我的情况下)。 所以,我杀了那些要求高分辨率系统定时器的程序。我的应用程序现在运行速度稍慢。现在每个请求将锁定16.5 ms而不是1 ms。我猜测背后的原因是线程排定的频率较低。 CPU使用率(如任务管理器中所示)也降为零。我毫不怀疑lock()仍然使用相当多的周期,但现在隐藏了。

在我的项目中,低CPU使用率是一个重要的设计因素。 USB请求的低1 ms延迟对整体设计也是有利的。所以(就我而言)解决方案是丢弃内置的lock(),并用正确实施的锁定机制替换它。我已经抛出有缺陷的System.IO.Ports.SerialPort赞成WinUSB,所以我没有担心:)

我做了一个小的控制台应用程序来演示所有这些,下午我,如果你有兴趣在副本(约100行代码)。

我想我回答我自己的问题,所以I'll刚刚离开这个位置的情况下,有人有兴趣...

+1

在我感兴趣的是,链接的文章使用'Semaphore'而不是'lock'来等待; *应该*更昂贵(它需要去OS层等)。顺便说一句,“仅约1ms”:1ms对于计算机来说是一个非常长的时间**。 –

+0

是的,马克,“只有1毫秒”来自线程调度器(afaik) – Mikael

+0

Henk的短时间为1毫秒,工作量很小。但“设备”是一台复杂的机器。我的印象是,我可以创建一组线程来控制机器的各个(独立)部分,而不会有任何不必要的等待。每秒只有1000个控制I/O操作是可能的,因此大多数线程将等待获取该锁。显然,这是使用内置锁()的糟糕设计,但如果我切换到更便宜的锁机制,设计良好)它几乎就像内置锁()使用某种忙碌的等待? – Mikael

回答

5

没有,对不起,这是不可能的。没有任何情况下你有3个线程,其中2个阻塞在锁上,1个阻塞在一个需要一毫秒的I/O操作可以使你的CPU利用率达到24%。链接的文章可能很有趣,但.NET Monitor类完全一样。包括CompareExchange()优化和等待队列。

您可以达到24%的唯一方法是通过其他在您的程序中运行的代码。通用循环窃取器是您每秒钟敲击上千次的UI线程。非常容易以这种方式刻录核心。一个经典的错误,人类的眼睛无法快速阅读。随着你进一步推断,你写了一个不更新UI的测试程序。因此不会烧核心。

分析器当然会告诉你这些周期的确切位置。这应该是你的下一步。

+0

分析器显示Monitor.Enter使用的循环数量特别高。这可能是由于我的系统在1ms线程调度和lock()中的某种设计缺陷相结合的情况下运行。我在原始问题中添加了我的发现。 – Mikael

+0

“1 ms线程调度”没有什么特别之处,只需使用Chrome即可。它也称为timeBeginPeriod(1)。许多SO用户喜欢用Chrome浏览SO,他们从不抱怨Monitor.Enter或锁定中的错误。我不买。您需要将您的断言提交给Microsoft支持。 –