更新 - 我发现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行代码)。
我想我回答了自己的问题所以我会把这个放在这里以防有人感兴趣...
答案 0 :(得分:5)
不,对不起,这是不可能的。在没有情况下你有3个线程,其中2个在锁上阻塞,1个阻塞在I / O操作上需要一毫秒才能获得24%的cpu利用率。链接的文章可能很有趣,但.NET Monitor类完全相同。包括CompareExchange()优化和等待队列。
获得24%的唯一方法是通过程序中运行的其他代码。使用常见的循环窃取程序是你每秒击打一千次的UI线程。这种方式非常容易刻录核心。一个经典的错误,人眼无法快速阅读。随后进一步推断,您编写了一个不会更新UI的测试程序。因此不会燃烧核心。
分析器当然会告诉您这些周期的确切位置。这应该是你的下一步。