Windows上的快速计数信号量

Fast counting semaphore on Windows?

本文关键字:信号量 Windows      更新时间:2023-10-16

首先,我知道它可以用互斥锁和条件变量来实现,但我想要最有效的实现。当没有争用时,我想要一个具有快速路径的信号量。在Linux上,这很容易使用futex;例如,下面是等待语句:

if (AtomicDecremenIfPositive(_counter) > 0) return; // Uncontended
AtomicAdd(&_waiters, 1);
do
{
    if (syscall(SYS_futex, &_counter, FUTEX_WAIT_PRIVATE, 0, nullptr, nullptr, 0) == -1) // Sleep
    {
        AtomicAdd(&_waiters, -1);
        throw std::runtime_error("Failed to wait for futex");
    }
}
while (AtomicDecrementIfPositive(_counter) <= 0);
AtomicAdd(&_waiters, -1);

和职位:

AtomicAdd(&_counter, 1);
if (Load(_waiters) > 0 && syscall(SYS_futex, &_counter, FUTEX_WAKE_PRIVATE, 1, nullptr, nullptr, 0) == -1) throw std::runtime_error("Failed to wake futex"); // Wake one

起初我认为Windows只是使用NtWaitForKeyedEvent()。问题是它不是直接替换,因为它在进入内核之前不会自动检查_counter的值,因此可能会错过NtReleaseKeyedEvent()的唤醒。更糟糕的是,NtReleaseKeyedEvent()会阻塞。最好的解决方案是什么?

Windows自带CreateSemaphore信号量。除非您在使用常规方法时遇到某种记录的性能问题,否则您甚至不应该考虑脆弱的或特定于硬件的优化。

我认为这样的东西应该工作:

// bottom 16 bits: post count
// top 16 bits: wait count
struct Semaphore { unsigned val; }
wait(struct Semaphore *s)
{
retry:
    do
        old = s->val;
        if old had posts (bottom 16 bits != 0)
            new = old - 1
            wait = false
        else
            new = old + 65536
            wait = true
    until successful CAS of &s->val from old to new
    if wait == true
        wait on keyed event
        goto retry;
}
post(struct Semaphore *s)
{
    do
        old = s->val;
        if old had waiters (top 16 bits != 0)
            // perhaps new = old - 65536 and remove the "goto retry" above?
            // not sure, but this is safer...
            new = old - 65536 + 1
            release = true
        else
            new = old + 1
            release = false
    until successful CAS of &s->val from old to new
    if release == true
        release keyed event
}

edit:这就是说,我不确定这会对你有多大帮助。您的线程池通常应该足够大,以便线程始终准备好处理您的请求。这意味着不仅要等待,而且帖子总是走慢路,到达内核。因此,计数信号量可能是您并不真正关心仅限用户空间的快速路径的一种原语。标准的Win32信号量应该足够好了。也就是说,我很高兴被证明是错的!

我赞成你的第一个想法,例如临界区和条件变量。临界区足够快,并且在休眠前使用联锁操作。或者,您可以尝试使用SRWLocks而不是临界区。条件变量(和SRWLocks)非常快——它们唯一的问题是XP上没有条件,但也许您不需要针对这个平台。

Qt有各种各样的东西,比如QMutex, QSemaphore,这些都是在精神上实现的,就像你在你的问题中提出的。

实际上,我建议用通常的操作系统提供的同步原语替换futex的东西;这应该无关紧要,因为这是一个缓慢的路径。