用原子做饱和度

时间:2017-08-04 10:50:53

标签: c++ multithreading c++11 atomic

我一直在寻找一种方法来保护一个整数以原子方式增加并使用绑定检查。 我已经四处寻找其他帖子,但似乎没有一个好的解决方案(有些是预先C ++ 11)。

我需要的是如下图书馆:

class bounded_atomic_uint
{
    private:
    uint32_t ctr;
    uint32_t max;
    mutex mtx;

    public:
    bounded_atomic_uint(uint32_t max = UINT32_MAX) : ctr(0), max(max) {}
    ~bounded_atomic_uint() = default;
    // make in uncopyable and un-movable
    bounded_atomic_uint(bounded_atomic_uint&&) = delete;
    bounded_atomic_uint& operator=(bounded_atomic_uint&&) = delete;

    bool inc();
    bool dec();
    uint32_t get();
};

bool bounded_atomic_uint::inc() {
    lock_guard<mutex> lck (mtx);
    if (ctr < max) {
        ctr++;
        return true;
    }
    else
    {
        cout << "max reached (" << max << ")" << endl; // to be removed!
        return false; // reached max value
    }
}

bool bounded_atomic_uint::dec() {
    lock_guard<mutex> lck (mtx);
    if (ctr > 0) {
        ctr--;
        return true;
    }
    else {
        cout << "min reached (0)" << endl; // to be removed!
        return false; // reached min value
    }
}

uint32_t bounded_atomic_uint::get() {
    lock_guard<mutex> lck (mtx);
    return ctr;
}

用作:

#include <iostream>
#include <mutex>
#include <cstdint>

using namespace std;

int main() {

    bounded_atomic_uint val(3);

    if (val.dec())
        cout << "error: dec from 0 succeeded !!" << endl;
    cout << val.get() << endl; // make sure it prints 0
    val.inc();
    val.inc();
    cout << val.get() << endl;
    if (!val.inc())
        cout << "error: havent reached max but failed!!" << endl;

    if (val.inc())
        cout << "error max not detected!!" << endl;

    cout << val.get() << endl;

    return 0;
}

有没有更简单的方法(或更正确)这样做? std :: atomic和boost :: atomic似乎都没有办法避免越界检查(在锁内)。

如果没有,这个简单类是否足够好? 或者我在这里遗漏了什么?

注意,库中的 couts 将在实际使用时删除!

1 个答案:

答案 0 :(得分:3)

这就是你的问题is off-topic的问题,所以让我回答一下我们如何实施这个问题,这是关于主题的问题。非常有趣。

首先,从您的示例中删除锁定并将int替换为atomics

class bounded_atomic_uint
{
    private:
    atomic<uint32_t> ctr;
    uint32_t max;

    public:
    bounded_atomic_uint(uint32_t max = UINT32_MAX) : ctr(0), max(max) {}
    ~bounded_atomic_uint() = default;
    // make in uncopyable and un-movable
    bounded_atomic_uint(bounded_atomic_uint&&) = delete;
    bounded_atomic_uint& operator=(bounded_atomic_uint&&) = delete;

    bool inc();
    bool dec();
    uint32_t get();
};

bool bounded_atomic_uint::inc() {
    if (ctr < max) {
        ctr++;
        return true;
    }
    else
    {
        cout << "max reached (" << max << ")" << endl; // to be removed!
        return false; // reached max value
    }
}

此代码的问题在于,在边界检查和增量之间,值可能已更改。因此,您只能保证在没有争用的情况下不会突破界限。

您可以通过确保增量值在两者之间没有变化来轻松解决此问题。这正是compare_exchange提供的内容:

bool bounded_atomic_uint::inc() {
    while(true) {
        auto ctr_old = ctr.load();
        if (ctr_old < max) {
            if(ctr.compare_exchange_weak(ctr_old, ctr_old + 1)) {
                return true;
            }
        }
        else
        {
            cout << "max reached (" << max << ")" << endl; // to be removed!
            return false; // reached max value
        }
    }
}

现在,如果计数器在边界检查和增量写入之间发生变化,compare_exchange_weak将失败,所以我们必须再试一次。如果数字在此期间超过了界限,我们将在下一个循环迭代中检测到这个并相应地退出。请注意,如果忽略compare_exchange的虚假失败*,那么只有在对原子进行实际并发写入时才需要循环,因此这个实现实际上是lock-free

我们可以通过将原子重复加载到compare_exchange中来使这一点略微提高效率(请记住compare_exchange将原子的实际值写回第一个参数):

bool bounded_atomic_uint::inc() {
    auto ctr_old = ctr.load();
    do {
        if (ctr_old >= max) {
            cout << "max reached (" << max << ")" << endl; // to be removed!
            return false; // reached max value
        }
    } while(!ctr.compare_exchange_weak(ctr_old, ctr_old + 1));
    return true;
}

*我们可以通过使用compare_exchange_strong来消除虚假失败,但是因为我们必须循环,所以实际上没有充分的理由这样做。