std::atomic.compare_and_exchange_strong() fails

std::atomic.compare_and_exchange_strong() fails

本文关键字:fails strong and atomic compare std exchange      更新时间:2023-10-16

我对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

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

很难说没有mvce,但问题很可能是由于填充引起的。CCD_ 9在概念上使用CCD_。由于对齐要求,您的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具有该属性)。

例如,项目可能如下所示:

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)的访问时,内存排序变得相关。然而,原子上所有操作的默认内存顺序是memory_order_seq_cst,这是最强的。