C# 锁语句性能

本文关键字:性能 语句 | 更新日期: 2023-09-27 17:56:11

更新 - 我找到了lock()疯狂地吃CPU周期的原因。我在最初的问题之后添加了此信息。这一切都变成了一堵文字墙,所以:

博士在某些情况下,如果系统使用高分辨率系统计时器运行,则 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 毫秒。

更新:lock()疯狂吃CPU的原因是某些应用程序使用winmm.dll中的timeBeginPeriod()提高了整个系统的计时器分辨率。在我的案例中,罪魁祸首是谷歌浏览器和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行代码),请告诉我。

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

C# 锁语句性能

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

达到 24% 的唯一方法是通过程序中运行的其他代码。 常见的循环窃取程序是您每秒冲击一千次的 UI 线程。 这样很容易烧芯。 一个经典的错误,人眼读不懂那么快。 通过进一步推断,您随后编写了一个不更新 UI 的测试程序。 因此不会烧毁核心。

分析器当然会准确地告诉你这些周期的去向。 这应该是你的下一步。