我正在重新发明C ++中线程池的轮子。我已经从代码中删除了几乎所有的锁,除了以下构造的多个实例:
std::atomic_size_t counter;
void produce() {
++counter;
}
void try_consume() {
if (counter != 0) {
--counter;
// ...
}
else {
// ...
}
}
所以,我需要这个函数的线程安全无锁版本:
bool take(std::atomic_size_t& value) {
if (value != 0) {
--value;
return true;
}
return false;
}
我知道有一个解决方案:使用boost::lockfree::queue<std::monostate>
,其中pop()
完成工作。有没有更好/更快的解决方案?
答案 0 :(得分:2)
您正在实施的构造是计数锁定,或counting semaphore。使用具有trylock
版本的库而不是自己编辑的库,这样您就可以获得优化的OS支持的睡眠/唤醒。或者,如果trylock
(又名take
)失败,您是否总是要做有用的工作?
请注意,您可以避免在实现自己的锁时使用任何传统锁,但是&#34;无锁&#34;是一个技术术语,与锁定的含义不同。根据定义,消费者在计算机科学意义上可能不是lock-free,因为如果生产者线程被阻止,它可能会永远等待。相关:Lock-free Progress Guarantees
CAS很好。如果它看到计数器已经为0并且具有纯负载(通常使用compare_exchange_weak
),则确保您的函数根本不运行memory_order_relaxed
。当其他线程试图递增它时,你不希望你的CPU在这个位置上锤击,这样你的线程就会看到一个非零值。
另一个选项是已签名的计数器,并将比较更改为>= 0
。检查fetch_add(-1)
结果中的过冲,如果是,请更正。 (看到计数器暂时为负的线程只看到它&#34;锁定&#34;)。但这通常不比CAS重试循环好; CAS失败很少,除非争用非常高。用于校正过冲的额外原子操作可能会花费与CAS重试相同(或更多)的成本。