std :: atomic.compare_and_exchange_strong()失败

时间:2015-11-28 16:35:16

标签: c++ c++11

我对C ++很陌生,不得不在原子操作上做一些练习。 我正在实现一个AtomicHashSet - 但我感到困惑的是compare_and_exchange_strong()的行为与我预期的不同。

作为内部数据结构,我使用了一个std :: atomic-instances数组:

std::atomic<Item<T>> data[N] = {};

观察问题的关键部分如下:

bool insert(const T &key) {
    if (keysStored.load() == N) {
        return false;
    }

    size_t h = this->hash(key);



    for (size_t i = 0; i < N; i++) {
        size_t pos = (h + i) % N;
        data[pos].load(); //No idea why that is needed...
        Item<T> atPos = data[pos].load();

        if (atPos.dataRef == &key) {
            return false;
        }
        if (atPos.dataRef == nullptr && atPos.state == BucketState::Empty) {
            Item<T> atomDesired(&key, BucketState::Occupied);

            if (data[pos].compare_exchange_strong(atPos, atomDesired)) {
                keysStored++;
                return true;
            }
        }
    }

    return false;
}

Item的定义如下:

enum class BucketState { Empty, Occupied, Deleted };

template<typename T>
struct Item {
Item(): dataRef(nullptr), state(BucketState::Empty) {}
Item(const T* dataRef, BucketState state) : dataRef(dataRef), state(state) {}

const T* dataRef;
BucketState state;
};

我做了一些断言测试(插入一个元素两次,检查keyStored等)。使用此代码他们会成功 - 但如果我删除了无意义的data[pos].load();调用,则由于compare_exchange_strong()返回false而失败。这种奇怪的失败行为仅在第一次调用函数时发生......

我还使用调试器进行了检查 - atPos的值与data[pos]中的相同 - 所以在我的理解中,ces应该进行交换并返回true

另一个问题:我是否必须使用特殊的内存顺序来确保原子(以及线程安全)行为?

1 个答案:

答案 0 :(得分:2)

没有mvce很难说,但问题很可能是由于填充造成的。 std::atomic.compare_and_exchange_strong概念上使用memcmp将当前状态与预期状态进行比较。由于对齐要求,Item结构的大小在64位机器上是16个字节(两个指针),但实际上只有12个对其值有贡献(指针为8,枚举为4)。

所以声明

Item<T> atPos = data[pos].load();

只复制前12个字节,但std::atomic.compare_and_exchange_strong将比较所有16个。为了解决这个问题,您可以明确指定BucketState的基础类型为与指针大小相同的整数类型(通常是size_t和uintptr_t有那个属性。)

E.g。项目可能如下所示:

enum class BucketState :size_t { Empty, Occupied, Deleted };

template<typename T>
struct Item {
    const T* dataRef;
    BucketState state;

    static_assert(sizeof(const T*) == sizeof(BucketState), "state should have same size as dataRef");
};

但是,我无法告诉您,为什么使用data[pos].load();语句会有所作为。如果我没有弄错,你对std :: memcmp的隐式调用会导致未定义的行为,因为它会读取未初始化的内存。

  

另一个问题:我是否必须使用特殊的内存顺序来确保原子(以及线程安全)行为?

简短的回答是不,你不需要。

长期的答案是,首先,对std :: atomics的访问始终是线程安全的和原子的(那些不一样)。当你想使用那些原子来保护对非原子共享内存(如if (dataAvalialbe) //readSharedmemory)的访问时,内存排序就变得相关了。但是,atomics上所有操作的默认内存排序是memory_order_seq_cst,这是最强的。