我目前正在重新发明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()
可以完成工作。有没有更好/更快的解决方案? 最佳答案
您要实现的构造是计数锁定或counting semaphore。使用具有trylock
版本的库中的库来代替自己的库,这样就可以优化操作系统支持的睡眠/唤醒功能。还是如果trylock
(aka take
)失败了,您是否总是有工作要做?
请注意,在实现自己的锁时可以避免使用任何传统锁,但是“无锁”是一个技术术语,其含义与无锁不同。从计算机科学的 Angular 来看,从定义上讲,消费者几乎不可能是lock-free,因为如果生产者线程被阻塞,它可能会一直处于等待状态。相关:Lock-free Progress Guarantees
CAS很好。只要确保函数在纯负载(通常是compare_exchange_weak
)下看到计数器已经为0的情况下,就根本不运行memory_order_relaxed
即可。您不希望您的CPU在其他线程试图递增该位置时对其进行锤击,因此您的线程将看到非零值。
另一个选项是签名计数器,然后将比较更改为>= 0
。检查fetch_add(-1)
的结果是否过冲,如果正确,请更正。 (将计数器视为暂时为负的线程只会将其“锁定”)。但这通常不比CAS重试循环更好。除非争端很高,否则失败的CAS很少见。额外的原子操作来纠正过冲可能比CAS重试花费大约(或更多)的费用。
关于c++ - 无锁 “decrement if not zero”,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/47650290/