简体   繁体   English

C#锁语句性能

[英]c# lock statement performance

Update - I have found the cause of lock() eating CPU cycles like crazy. 更新-我发现lock()导致疯狂的CPU周期的原因。 I added this information after my original question. 我在原始问题后添加了此信息。 This all turned out to be a wall of text so: 事实证明,所有这些都是一堵墙,所以:

TL;DR The c# built-in lock() mechanism will, under some circumstances, use an unusual amount of CPU time if your system is running with a high resolution system timer. TL; DR如果您的系统使用高分辨率系统计时器运行,则在某些情况下,c#内置lock()机制将使用不寻常的CPU时间。

Original question: 原始问题:

I have an application that accesses a resource from multiple threads. 我有一个从多个线程访问资源的应用程序。 The resource is a device attached to USB. 资源是连接到USB的设备。 Its a simple command/response interface and I use a small lock() block to ensure that the thread that sends a command also gets the response. 它是一个简单的命令/响应接口,并且我使用了一个小的lock()块来确保发送命令的线程也可以获取响应。 My implementation uses the lock(obj) keyword: 我的实现使用lock(obj)关键字:

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

When I access this from 3 threads as fast as possible (in a tight loop) the CPU usage is about 24% on a high-end computer. 当我以最快的速度(在一个紧密的循环中)从3个线程访问此线程时,高端计算机上的CPU使用率约为24%。 Due to the nature of the USB port only about 1000 command/response operations are performed per second. 由于USB端口的性质,每秒仅执行约1000次命令/响应操作。 Then I implemented the lock mechanism described here SimpleExclusiveLock and the code now looks similar to this (some try / catch stuff to release the lock in case of an I/O exception is removed): 然后,我实现了这里描述的锁机制SimpleExclusiveLock ,现在代码看起来与此类似(一些try / catch东西可以在I / O异常被删除的情况下释放锁):

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

Using this implementation the CPU usage drops to <1% with the same 3 thread test program while still getting the 1000 command/response operations per second. 使用此实现,在使用相同的3线程测试程序的情况下,CPU使用率降至<1%,同时仍能每秒获得1000个命令/响应操作。

The question is: What, in this case, is the problem using the built-in lock() keyword? 问题是:在这种情况下,使用内置的lock()关键字是什么问题?

Have I accidentally stumbled upon a case where the lock() mechanism has exceptionally high overhead? 我是否偶然发现了lock()机制具有非常高的开销的情况? The thread that enters the critical section will hold the lock for only about 1 ms. 进入临界区的线程将仅将锁保持大约1 ms。

Update: The cause of lock() eating CPU like crazy is that some application has increased the timer resolution for the whole system using timeBeginPeriod() in winmm.dll. 更新: lock()疯狂地吞噬CPU的原因是某些应用程序使用timeBeginPeriod()增加了整个系统的计时器分辨率。 The culprits in my case are Google Chrome and SQL Server - they requested a 1 ms system timer resolution using: 在我的案例中,罪魁祸首是谷歌浏览器和SQL Server-他们要求使用1 ms的系统计时器分辨率:

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

I found this out by using the powercfg tool: 我通过使用powercfg工具发现了这一点:

powercfg -energy duration 5 

Due to some sort of design flaw in the built-in lock() statement this increased timer resolution eats CPU like crazy (at least in my case). 由于内置的lock()语句中存在某种设计缺陷,因此这种提高的计时器分辨率会使CPU疯狂(至少在我的情况下)。 So, I killed the programs that request high resolution system timer. 因此,我杀死了需要高分辨率系统计时器的程序。 My application now runs a bit slower. 我的应用程序现在运行速度稍慢。 Each request will now lock for 16.5 ms instead of 1 ms. 现在,每个请求将锁定16.5毫秒而不是1毫秒。 The reason behind that I guess is that the threads are scheduled less frequently. 我猜想其背后的原因是线程调度的频率较低。 The CPU usage (as shown in Task Manager) also dropped to zero. CPU使用率(如任务管理器中所示)也降为零。 I have no doubt that lock() still uses quite a few cycles but that is now hidden. 我毫不怀疑lock()仍然使用了很多周期,但是现在被隐藏了。

In my project low CPU use is an important design factor. 在我的项目中,低CPU使用率是重要的设计因素。 The low 1 ms latency of USB requests are also positive for the overall design. USB请求的低1 ms延迟对于整个设计也是有利的。 So (in my case) the solution is to discard the built-in lock() and replace it with a properly implemented lock mechanism. 因此,(在我的情况下)解决方案是丢弃内置的lock()并用正确实现的锁定机制替换它。 I already threw out the flawed System.IO.Ports.SerialPort in favor of WinUSB so I have no fears :) 我已经淘汰了有缺陷的System.IO.Ports.SerialPort ,转而使用WinUSB,所以我不用担心:)

I made a small console-application to demonstrate all of this, pm me if you are interested in a copy (~100 lines of code). 我编写了一个小型控制台应用程序来演示所有这些内容,如果您对副本感兴趣(〜100行代码),请按我的说明。

I guess I answered my own question so I´ll just leave this here in case someone is interested... 我想我回答了我自己的问题,所以只要有人感兴趣,我就把它留在这里。

No, sorry, this is not possible. 不,抱歉,这是不可能的。 There's no scenario where you have 3 threads with 2 of them blocking on the lock and 1 blocking on an I/O operation that takes a millisecond can get you 24% cpu utilization. 在任何情况下,您都不会拥有3个线程,其中2个线程在锁上阻塞,而1个线程在I / O操作上阻塞(花费一毫秒)可以使您的CPU利用率达到24%。 The linked article is perhaps interesting, but the .NET Monitor class does the exact same thing. 链接的文章可能很有趣,但是.NET Monitor类执行的操作完全相同。 Including the CompareExchange() optimization and the wait queue. 包括CompareExchange()优化和等待队列。

The only way you can get to 24% is through other code that runs in your program. 达到24%的唯一方法是通过程序中运行的其他代码。 With the common cycle stealer being the UI thread that you pummel a thousand times per second. 常见的循环窃取程序是UI线程,您每秒要击打一千次。 Very easy to burn core that way. 这样很容易烧核。 A classic mistake, human eyes can't read that fast. 这是一个经典的错误,人眼无法快速阅读。 With the further extrapolation that you then wrote a test program that doesn't update UI. 通过进一步的推断,您然后编写了一个不会更新UI的测试程序。 And thus doesn't burn core. 因此不会烧毁核心。

A profiler will of course tell you exactly where those cycles go. 探查器当然会告诉您这些周期的确切位置。 It should be your next step. 这应该是您的下一步。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM