std :: atomic.compare_and_exchange_strong()失败 [英] std::atomic.compare_and_exchange_strong() fails

查看:253
本文介绍了std :: atomic.compare_and_exchange_strong()失败的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我对C ++还是很陌生,必须对原子操作进行一些练习。
我正在实现AtomicHashSet-但是我对 compare_and_exchange_strong()感到困惑,因为它与我期望的有所不同。

I am pretty new to C++ and have to do some exercises on atomic operations. I am implementing a AtomicHashSet - but I get confused by compare_and_exchange_strong()behaving different than I would expect it.

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

As internal data structure I use an array of std::atomic-instances:

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

观察问题的基本部分如下:

The essential part observing the problems is the following:

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;
}

项目已定义像这样:

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()而失败。 code>返回 false 。这种奇怪的失败行为只有在第一次调用该函数时才会发生...

I do some assertion tests (inserting an element twice, checking keyStoredetc.). With this code they succeed - but if I remove the nonsense data[pos].load(); call they fail due to compare_exchange_strong() returning false. This strange failing behavior occurs only the first time the function is called...

我还用调试器进行了检查-atPos的值与<$ c中的值相同$ c> data [pos] -因此,根据我的理解,ces应该进行交换并拒绝 true

I also checked with a debugger - the value of atPos is the same as in data[pos] - so in my understanding ces should do the exchange and returjn true.

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

Another question: Do I have to use a special memory order to ensure atomic (and therefore threadsafe) behaviour?

推荐答案

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

It is hard to say without a mvce, but the problem arises most probably due to padding. std::atomic.compare_and_exchange_strong conceptually uses memcmp to compare the current state with the expected one. Due to alignment requirements, the size of your Itemstructure will be 16 bytes on a 64 bit machine (two pointers) but only 12 of them actually contribute to its value (8 for the pointer and 4 for the enum).

因此,语句

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

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

only copies the first 12 bytes, but std::atomic.compare_and_exchange_strong will compare all 16. To solve this you could explicitly specify the underlying type of BucketState to be an integral type that has the same size as a pointer (usually size_t and uintptr_t have that property).

例如项目可能看起来像这样:

E.g. Item could look like this:

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的隐式调用会导致未定义的行为,因为它将读取未初始化的内存。

I can't tell you however, why using the statement data[pos].load(); makes a difference. If I'm not mistaken, your implicit call to std::memcmp causes undefined behavior, as it will read uninitialized memory.


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

Another question: Do I have to use a special memory order to ensure atomic (and therefore threadsafe) behaviour?

简短的回答是不,您不需要

The short answer is no, you don't need to.

长的答案是,首先,对std :: atomics的访问始终是线程安全的和原子的(两者不相同)。当您要使用这些原子来保护对非原子共享内存的访问时,内存排序就变得很重要(例如 if(dataAvalialbe)// readSharedmemory )。但是,对原子的所有操作的默认内存排序为memory_order_seq_cst,这是最强的。

The long answer is that first of all, accesses to std::atomics are always thread safe and atomic (those are not the same). Memory ordering become relevant, when you want to use those atomics to guard access to non-atomic shared memory (like if (dataAvalialbe) //readSharedmemory). However, the default memory ordering for all operations on atomics is memory_order_seq_cst, which is the strongest there is.

这篇关于std :: atomic.compare_and_exchange_strong()失败的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆