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:因为有请求的事件使用的详细信息,所以这里是。我实现了一个可以在常规模式和维护模式之间切换的知识库。有些操作只是维护操作,有些操作只是常规操作,有些操作可以在两种模式下工作,但有些操作维护速度更快,有些操作在常规模式下更快。在启动时,每个操作都需要知道它是处于维护模式还是常规模式,因为逻辑发生了变化(或者操作完全拒绝执行)。用户可以不时要求在维护和常规模式之间切换。这很少见。当此请求到达时,旧模式中的新操作无法启动(请求执行此操作失败)并且应用程序等待旧模式中的当前操作完成,然后切换模式。因此,轻量事件是此数据结构的一部分:除了模式切换之外的操作必须很快,因此需要快速设置/重置/等待事件。
答案 0 :(得分:1)
如果您可以放弃对Windows 7的支持,另一个答案非常好。
但是在Win7上,如果您从多个线程中多次设置/重置事件,但只需要很少睡眠,那么建议的方法就很慢。
相反,我使用由临界区保护的布尔值,条件变量唤醒/睡眠。
wait方法将进入内核,在SleepConditionVariableCS API上进行睡眠,这是预期的以及您想要的。
但是设置和重置方法将完全在用户模式下起作用:设置单个布尔变量非常快,即在99%的情况下,关键部分将实现用户模式无锁魔术。
答案 1 :(得分:0)
从win8开始,使用WaitOnAddress
的最佳解决方案(就地WaitForSingleObject
,WakeByAddressAll
(对{em> NotificationEvent 来说就像SetEvent
一样工作) WakeByAddressSingle
(像 SynchronizationEvent 一样工作)。更多阅读 - WaitOnAddress lets you create a synchronization object
实施可以是下一步:
class LightEvent
{
BOOLEAN _Signaled;
public:
LightEvent(BOOLEAN Signaled)
{
_Signaled = Signaled;
}
void Reset()
{
_Signaled = FALSE;
}
void Set(BOOLEAN bWakeAll)
{
_Signaled = TRUE;
(bWakeAll ? WakeByAddressAll : WakeByAddressSingle)(&_Signaled);
}
BOOL Wait(DWORD dwMilliseconds = INFINITE)
{
BOOLEAN Signaled = FALSE;
while (!_Signaled)
{
if (!WaitOnAddress(&_Signaled, &Signaled, sizeof(BOOLEAN), dwMilliseconds))
{
return FALSE;
}
}
return TRUE;
}
};
不要忘记为链接器输入添加Synchronization.lib
。
这个新api的代码非常有效,它们不会为等待(如事件)创建内部内核对象,但为此目标使用新的api ZwAlertThreadByThreadId
ZwWaitForAlertByThreadId
特殊设计。
在win8之前如何实现这一点?首先看起来很琐碎 - boolen varitable +事件句柄。并且必须看起来像:
void Set()
{
SetEvent(_hEvent);
// Sleep(1000); // simulate thread innterupted here
_Signaled = true;
}
void Reset()
{
_Signaled = false;
// Sleep(1000); // simulate thread innterupted here
ResetEvent(_hEvent);
}
void Wait(DWORD dwMilliseconds = INFINITE)
{
if(!_Signaled) WaitForSingleObject(_hEvent);
}
但这段代码确实不正确。我们在Set
(Reset
)中执行2操作的问题 - 更改_Signaled
和_hEvent
的状态。并且无法通过原子/互锁操作从用户模式执行此操作。这意味着线程可以在这两个操作之间中断。假设并发调用Set
和Reset
中有2个不同的线程。在大多数情况下,操作将按下一个顺序执行,例如:
SetEvent(_hEvent);
_Signaled = true;
_Signaled = false;
ResetEvent(_hEvent);
这里一切都好。但可能和下一个订单(取消注释一个Sleep
进行测试)
SetEvent(_hEvent);
_Signaled = false;
ResetEvent(_hEvent);
_Signaled = true;
结果_hEvent
将处于重置状态,当_Signaled
为true
时。
自己实现这个原子,没有os支持也不是简单的,但是可能的。但我首先要寻找这个的用法 - 为了什么?事件就像行为这正是你需要的任务吗?