更新 - 我已经找到了造成
当我以尽可能快的速度(在一个紧密循环中)从3个线程访问此内容时,在高端计算机上的CPU使用率约为24%。由于USB端口的特性,每秒只执行大约1000个命令/响应操作。 然后,我实现了在这里描述的锁机制SimpleExclusiveLock,现在代码看起来类似于这样(一些
使用这个实现方式,当使用相同的3线程测试程序时,CPU使用率降至<1%,同时仍然能够获得1000个命令/响应操作每秒。
问题是:在这种情况下,使用内置的lock()关键字存在什么问题?
我是否偶然发现了一个lock()机制开销异常高的情况?进入临界区的线程将仅持有锁约1毫秒。
更新: 造成lock()占用大量CPU的原因是某些应用程序使用winmm.dll中的timeBeginPeriod()增加了整个系统的计时器分辨率。在我的情况下,罪魁祸首是Google Chrome和SQL Server - 它们使用以下代码请求了1毫秒的系统计时器分辨率:
我通过使用powercfg工具发现了这一点:
由于内置的lock()语句存在某种设计缺陷,这种增加的计时器分辨率会像疯狂一样消耗CPU(至少在我的情况下是这样)。因此,我杀掉了请求高分辨率系统计时器的程序。现在我的应用程序运行速度变慢了一点。每个请求现在将锁定16.5毫秒而不是1毫秒。我想原因是线程被调度得更少了。任务管理器中显示的CPU使用率也降至零。我毫不怀疑lock()仍然使用了相当多的周期,但现在这已经隐藏起来了。
在我的项目中,低CPU使用率是一个重要的设计因素。USB请求的低1 ms延迟对整体设计也是有益的。因此(在我的情况下),解决方案是放弃内置的lock()并用正确实现的锁机制替换它。我已经扔掉了有缺陷的System.IO.Ports.SerialPort,转而使用WinUSB,所以我没有恐惧 :)
我制作了一个小型控制台应用程序来演示所有这些内容,如果您有兴趣获得副本,请私信联系我(约100行代码)。
我想我回答了自己的问题,所以我只是把这个留在这里,以防有人感兴趣...
lock()
消耗大量CPU周期的原因。我在最初的问题之后添加了这些信息。这些都变成了一堵文字墙,所以:
简短概述
在某些情况下,C#内置的lock()
机制将会使用异常的CPU时间,如果您的系统正在运行高分辨率系统计时器。
最初的问题:
我有一个应用程序从多个线程访问资源。该资源是连接到USB的设备。它是一个简单的命令/响应接口,我使用一个小的lock()
块来确保发送命令的线程也获得响应。
我的实现使用lock(obj)
关键字:
lock (threadLock)
{
WriteLine(commandString);
rawResponse = ReadLine();
}
当我以尽可能快的速度(在一个紧密循环中)从3个线程访问此内容时,在高端计算机上的CPU使用率约为24%。由于USB端口的特性,每秒只执行大约1000个命令/响应操作。 然后,我实现了在这里描述的锁机制SimpleExclusiveLock,现在代码看起来类似于这样(一些
try
/catch
的代码被删除以释放异常处理IO锁定):Lock.Enter();
WriteLine(commandString);
rawResponse = ReadLine();
Lock.Exit();
使用这个实现方式,当使用相同的3线程测试程序时,CPU使用率降至<1%,同时仍然能够获得1000个命令/响应操作每秒。
问题是:在这种情况下,使用内置的lock()关键字存在什么问题?
我是否偶然发现了一个lock()机制开销异常高的情况?进入临界区的线程将仅持有锁约1毫秒。
更新: 造成lock()占用大量CPU的原因是某些应用程序使用winmm.dll中的timeBeginPeriod()增加了整个系统的计时器分辨率。在我的情况下,罪魁祸首是Google 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毫秒而不是1毫秒。我想原因是线程被调度得更少了。任务管理器中显示的CPU使用率也降至零。我毫不怀疑lock()仍然使用了相当多的周期,但现在这已经隐藏起来了。
在我的项目中,低CPU使用率是一个重要的设计因素。USB请求的低1 ms延迟对整体设计也是有益的。因此(在我的情况下),解决方案是放弃内置的lock()并用正确实现的锁机制替换它。我已经扔掉了有缺陷的System.IO.Ports.SerialPort,转而使用WinUSB,所以我没有恐惧 :)
我制作了一个小型控制台应用程序来演示所有这些内容,如果您有兴趣获得副本,请私信联系我(约100行代码)。
我想我回答了自己的问题,所以我只是把这个留在这里,以防有人感兴趣...
Semaphore
而不是lock
进行等待; 这应该会更昂贵(需要进入操作系统层等)。顺便说一句,“只有大约1毫秒”:对于计算机来说,1毫秒是非常长的时间。 - Marc Gravelllock
可能会导致线程上下文切换,而这些上下文切换会消耗CPU。请使用SpinLock替换lock (threadLock)
,并查看结果。我怀疑你会得到类似于使用SimpleExclusiveLock
的结果。旋转操作可以防止上下文切换,除非等待时间特别长。 - Jim Mischel