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