在WinAPI/C++中是否有一些轻量级(因此快速)的事件?特别是,当事件被设置时,我对最小化等待事件所花费的时间很感兴趣(例如WaitForSingleObject()
)。以下是一个代码示例,以进一步说明我的意思:
#include <Windows.h>
#include <chrono>
#include <stdio.h>
int main()
{
const int64_t nIterations = 10 * 1000 * 1000;
HANDLE hEvent = CreateEvent(nullptr, true, true, nullptr);
auto start = std::chrono::high_resolution_clock::now();
for (int64_t i = 0; i < nIterations; i++) {
WaitForSingleObject(hEvent, INFINITE);
}
auto elapsed = std::chrono::high_resolution_clock::now() - start;
double nSec = 1e-6 * std::chrono::duration_cast<std::chrono::microseconds>(elapsed).count();
printf("%.3lf Ops/sec\n", nIterations / nSec);
return 0;
}
在3.85GHz Ryzen 1800X上,我每秒获得7209623.405个操作,意味着平均每个检查事件是否设置需要534个CPU时钟(或138.7纳秒)。
然而,我想在性能关键代码中使用此事件,其中大多数时间实际上设置了事件,因此只是一个特殊情况的检查,在这种情况下,控制流程进入不是性能关键的代码(因为这种情况很少发生)。
WinAPI事件(使用CreateEvent创建)由于安全属性和名称而变得笨重。它们用于进程间通信。也许WaitForSingleObject()很慢是因为即使事件被设置,它仍然从用户模式切换到内核模式,然后再切换回来。此外,这个函数必须为手动和自动复位事件表现出不同的行为,检查事件类型也需要时间。
我知道可以使用
atomic_flag
实现快速用户模式互斥锁(自旋锁)。可以通过在自旋循环中添加std::this_thread::yield()
来让其他线程在自旋时运行。对于事件,我不想完全等价于自旋锁,因为当事件未设置时,可能需要相当长的时间才能再次设置。如果每个需要设置事件的线程都开始自旋直到再次设置它,那将是对CPU电力的巨大浪费(虽然如果它们调用
std::this_thread::yield
,不应影响系统性能)。因此,我更喜欢临界区的类比,通常只在用户模式下执行工作,当它意识到需要等待时(超过自旋次数),它将切换到内核模式并在重量级同步对象(如互斥量)上等待。
UPDATE1: 我发现 .NET 有
ManualResetEventSlim
,但在 WinAPI / C++ 中找不到相应的东西。UPDATE2: 因为需要使用事件的详细信息,所以在这里提供。我正在实现一个知识库,可以在常规模式和维护模式之间切换。一些操作仅适用于维护模式,一些操作仅适用于常规模式,某些操作可以在两种模式下工作,但其中一些在维护模式下更快,另一些在常规模式下更快。每个操作在启动时都需要知道它是在维护模式还是常规模式下运行,因为逻辑会改变(或者操作根本不会执行)。用户可以随时请求在维护模式和常规模式之间切换,但这很少见。当此请求到达时,旧模式中不能启动新操作(尝试这样做将失败),应用程序等待旧模式中当前操作完成,然后切换模式。因此,轻量级事件是此数据结构的一部分:除了模式切换之外的操作必须快速完成,因此它们需要快速设置/重置/等待事件。
atomic_bool
与手动重置事件对象结合起来应该很容易。 当您设置或重置事件时,也设置或重置布尔值。 在等待事件之前,请检查布尔值;如果已设置,则不需要等待。(我想atomic_bool
实现效率很高,但是如果您愿意,也可以使用 Win32 原子操作。) - Harry Johnston