Winapi / C 中的轻型事件

Light event in WinAPI / C++

本文关键字:轻型 事件 Winapi      更新时间:2023-10-16

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/secn", nIterations / nSec);
  return 0;
}

在3.85GHz ryzen 1800x上,我每秒获得7209623.405操作,这意味着534 CPU时钟(或138.7纳秒(平均用于检查是否设置了该事件。

但是,我想在大多数情况下实际设置该事件中的事件,因此实际上是对特殊情况的检查 - 临界(因为这种情况很少(。

Winapi事件(使用CreateEvent创建(是重量重量,因为安全属性和名称。它们旨在用于过程间沟通。也许WaitForSingleObject()太慢了,因为即使设置了事件,它也会从用户转换为内核模式并返回。此外,此功能对于手动和自动重置事件的行为必须有所不同,并且该事件类型的检查也需要时间。

我知道可以使用atomic_flag实现快速的用户模式Mutex(旋转锁(。它的旋转环可以用std::this_thread::yield()扩展,以便在旋转时运行其他线程。

在事件中,我不希望完全等同于旋转锁,因为当未设置事件时,可能需要大量时间才能再次设置。如果每个需要事件集的线程开始旋转直到再次设置,那将是CPU电力的史诗般的浪费(尽管如果他们调用std::this_thread::yield,则不会影响系统性能(

因此,我宁愿一个关键部分的类比,通常只能在用户模式下进行工作,并且当意识到它需要等待(旋转(时,它会切换到内核模式并在重型同步对象上等待像一个互惠。

Update1:我发现.NET具有ManualResetEventSlim,但在Winapi/C 中找不到同等的。

update2:由于请求事件用法的详细信息,因此它们是。我正在实施一个可以在常规和维护模式之间切换的知识库。有些操作是仅维护的,有些操作是常规的,有些操作可以在两种模式下工作,但是其中一些操作的维护速度更快,有些则在常规模式下更快。开始后,每个操作都需要知道它是在维护还是常规模式下,随着逻辑的变化(或操作完全拒绝执行(。用户可以不时地在维护和常规模式之间请求切换。这很少见。当此请求到达时,在旧模式下没有新的操作可以启动(这样做的请求会失败(,并且该应用程序在旧模式下等待当前操作以完成,然后它切换模式。因此,轻事件是此数据结构的一部分:除模式切换以外的操作必须快速,因此他们需要快速设置/重置/等待事件。

从Win8开始使用WaitOnAddress的最佳解决方案(WaitForSingleObjectWakeByAddressAll(例如SetEvent(( notificationEventEvent (和WakeByAddressSingle(工作如 synchronizationevent (。更多读取 - 读取 - WaitonAddress允许您创建一个同步对象

实施可以是下一个:

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之前,如何实施它?首先看琐碎的 - 布伦·可罗运 事件句柄。并且必须看起来像:

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的状态。从用户模式进行原子/互锁操作,这绝不是这样做的。这意味着可以在这两个操作之间中断线程。假设并发调用SetReset中的2个不同的线程。在大多数情况下,操作将以下一个顺序执行:

  SetEvent(_hEvent);
  _Signaled = true;
  _Signaled = false;
  ResetEvent(_hEvent);

这里都可以。但可能会和下一个订单(未点击一个Sleep进行测试(

  SetEvent(_hEvent);
  _Signaled = false;
  ResetEvent(_hEvent);
  _Signaled = true;

结果_hEvent将处于重置状态,当_Signaledtrue

将其作为原子本身实现,而无需执行操作系统支持,这将不可能是多么可能。但是我首先要寻找这一点 - 为什么?像行为这样的事件是否正是您需要进行任务?

如果您可以放弃Windows 7的支持。

,另一个答案非常好。

在Win7上,如果您从多个线程设置/重置事件,但是只需要很少睡觉,建议的方法很慢。

相反,我使用的是由关键部分保护的布尔值,条件变量可唤醒/睡眠。

等待方法将在SleepConditionVariablecs API上进入内核,这是预期的和您想要的。

但是设置&amp;重置方法将完全在用户模式下工作:设置单个布尔变量非常快,即在99%的情况下,关键部分将执行其用户模式锁定免费魔法。