std :: atomic.compare_and_exchange_strong()失败 [英] std::atomic.compare_and_exchange_strong() fails
问题描述
我对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 keyStored
etc.). 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 Item
structure 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屋!