无锁“减少即使不为零”

时间:2017-12-05 09:37:53

标签: c++ thread-safety atomic lock-free

我正在重新发明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()完成工作。有没有更好/更快的解决方案?

1 个答案:

答案 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重试相同(或更多)的成本。