在Windows上快速计数信号量?

时间:2011-12-06 23:08:51

标签: c++ multithreading winapi synchronization semaphore

首先,我知道它可以使用互斥锁和条件变量来实现,但我希望实现最有效的实现。 当没有争用时,我想要一个具有快速路径的信号量。在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()会阻止。 什么是最好的解决方案?

4 个答案:

答案 0 :(得分:3)

Windows具有CreateSemaphore的本机信号量。除非您有正常方式执行某种记录性能问题,否则您甚至不应考虑脆弱或特定于硬件的优化。

答案 1 :(得分:2)

我认为这样的事情应该有效:

// 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
}

编辑:那就是说,我不确定这会对你有多大帮助。您的线程池通常应该足够大,以便线程始终准备好处理您的请求。这意味着不仅等待,而且帖子总是采用慢速路径并转到内核。因此,计算信号量可能是您不太关心仅用户空间的快速路径的原始信号。股票Win32信号量应该足够好。那就是说,我很高兴被证明是错的!

答案 2 :(得分:1)

我投票给你的第一个想法,例如关键部分和条件变量。关键部分足够快,并且在进入睡眠状态之前确实使用了互锁操作。或者,您可以尝试使用SRWLocks而不是关键部分。条件变量(和SRWLocks)非常快 - 它们唯一的问题是XP上没有条件,但是你可能不需要针对这个平台。

答案 3 :(得分:0)

Qt有各种各样的东西,比如QMutex,QSemaphore,它们的精神实现就像你在问题中提到的那样。

实际上,我建议用通常的OS提供的同步原语替换futex东西;这无关紧要,因为无论如何这是一条缓慢的道路。