c#锁定语句性能

时间:2014-04-02 08:43:54

标签: c# multithreading locking

更新 - 我发现lock()吃CPU周期的原因就像疯了一样。我在原始问题之后添加了这些信息。这一切都证明是一面文字:

TL; DR 在某些情况下,如果系统使用高分辨率系统计时器运行,c#内置lock()机制将使用不寻常的CPU时间。

原始问题:

我有一个从多个线程访问资源的应用程序。资源是连接到USB的设备。它是一个简单的命令/响应接口,我使用一个小lock()块来确保发送命令的线程也获得响应。 我的实现使用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 Server - 他们使用以下方法请求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行代码)。

我想我回答了自己的问题所以我会把这个放在这里以防有人感兴趣...

1 个答案:

答案 0 :(得分:5)

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

获得24%的唯一方法是通过程序中运行的其他代码。使用常见的循环窃取程序是你每秒击打一千次的UI线程。这种方式非常容易刻录核心。一个经典的错误,人眼无法快速阅读。随后进一步推断,您编写了一个不会更新UI的测试程序。因此不会燃烧核心。

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