我对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
。
另一个问题:我是否必须使用特殊的内存顺序来确保原子(以及线程安全)行为?
答案 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,这是最强的。