学习
std::atomic
和 std::memory_order
后,我想尝试基于 Microsoft 博客:Inside STL:智能指针,使用原子编写线程安全的
shared_ptr<T>
和 weak_ptr<T>
实现。我不确定在为 weak_ptr<T>::lock()
循环实现 compare_exchange_weak
时使用什么内存顺序。
控制块:
class control_block {
public:
constexpr control_block() : strong(1), weak(1) {}
void inc_strong() { strong.fetch_add(1, std::memory_order_relaxed); }
void inc_weak() { weak.fetch_add(1, std::memory_order_relaxed); }
int32_t dec_strong() { return strong.fetch_sub(1, std::memory_order_release); }
int32_t dec_weak() { return weak.fetch_sub(1, std::memory_order_release); }
int32_t use_count() { return strong.load(std::memory_order_relaxed); }
public:
std::atomic<int32_t> strong;
std::atomic<int32_t> weak;
};
共享指针
template<class T>
class shared_ptr {
public:
using element_type = T;
using weak_type = weak_ptr<T>;
friend class weak_ptr<T>;
public:
constexpr shared_ptr() noexcept: object(nullptr), blk(nullptr) {}
explicit shared_ptr(T *ptr) : object(ptr), blk(new control_block()) {}
shared_ptr(const shared_ptr &other) noexcept {
object = other.object;
blk = other.blk;
if (blk) {
blk->inc_strong();
}
}
shared_ptr(shared_ptr &&other) noexcept {
object = other.object;
blk = other.blk;
other.object = nullptr;
other.blk = nullptr;
}
shared_ptr<T> &operator=(const shared_ptr &other) noexcept {
if (this == &other) return *this;
release_strong_ref();
object = other.object;
blk = other.blk;
if (blk) {
blk->inc_strong();
}
return *this;
};
shared_ptr<T> &operator=(shared_ptr &&other) noexcept {
if (this == &other) return *this;
object = other.object;
blk = other.blk;
other.object = nullptr;
other.blk = nullptr;
return *this;
};
~shared_ptr() {
release_strong_ref();
}
public:
explicit operator bool() const noexcept { return blk != nullptr; }
[[nodiscard]] element_type *get() const noexcept { return object; }
[[nodiscard]] int32_t use_count() const noexcept { return blk ? blk->use_count() : 0; }
T &operator*() const noexcept { return *object; }
T *operator->() const noexcept { return object; }
private:
void release_strong_ref() noexcept {
if (!blk) return;
if (blk->dec_strong() == 1) {
std::atomic_thread_fence(std::memory_order_acquire);
delete object;
if (blk->dec_weak() == 1) {
std::atomic_thread_fence(std::memory_order_acquire);
delete blk;
}
}
}
private:
T *object;
control_block *blk;
};
和弱指针:
template<class T>
class weak_ptr {
public:
using element_type = T;
friend class shared_ptr<T>;
public:
constexpr weak_ptr() noexcept: object(nullptr), blk(nullptr) {};
weak_ptr(const weak_ptr &other) noexcept {
release_weak_ref();
object = other.object;
blk = other.blk;
if (blk) {
blk->inc_weak();
}
}
weak_ptr(weak_ptr &&other) noexcept {
object = other.object;
blk = other.blk;
other.object = nullptr;
other.blk = nullptr;
}
template<class Y>
weak_ptr(const shared_ptr<Y> &other) noexcept {
if (!other) {
object = nullptr;
blk = nullptr;
return;
}
object = other.object;
blk = other.blk;
blk->inc_weak();
}
~weak_ptr() {
release_weak_ref();
}
template<class Y>
weak_ptr<T> &operator=(const shared_ptr<Y> &other) noexcept {
release_weak_ref();
if (!other) {
object = nullptr;
blk = nullptr;
return *this;
}
object = other.object;
blk = other.blk;
blk->inc_weak();
return *this;
};
weak_ptr<T> &operator=(const weak_ptr &other) noexcept {
if (this == &other) return *this;
release_weak_ref();
object = other.object;
blk = other.blk;
if (blk) {
blk->inc_weak();
}
return *this;
};
weak_ptr<T> &operator=(weak_ptr &&other) noexcept {
if (this == &other) return *this;
object = other.object;
blk = other.blk;
other.object = nullptr;
other.blk = nullptr;
return *this;
};
public:
explicit operator bool() const noexcept { return blk != nullptr; }
shared_ptr<T> lock() noexcept {
if (!blk) return shared_ptr<T>();
int32_t old = blk->strong.load(std::memory_order_relaxed);
while (old > 0) {
if (blk->strong.compare_exchange_weak(old, old + 1, std::memory_order_acquire)) {
shared_ptr<T> ptr;
ptr.object = object;
ptr.blk = blk;
return ptr;
}
old = blk->strong.load(std::memory_order_relaxed);
}
return shared_ptr<T>();
}
[[nodiscard]]int32_t use_count() const noexcept { return blk ? blk->use_count() : 0; }
[[nodiscard]]bool expired() const noexcept { return use_count() == 0; }
private:
void release_weak_ref() noexcept {
if (!blk) return;
if (blk->dec_weak() == 1) {
std::atomic_thread_fence(std::memory_order_acquire);
delete blk;
}
}
private:
T *object;
control_block *blk;
};
在进行比较交换循环以检查强引用是否非零时,成功和失败内存顺序应该是什么?并且,在
shared_ptr<T>
和 weak_ptr<T>
的析构函数中,std::atomic_thread_fence(std::memory_order_acquire)
的内存顺序是否正确以阻止控制块和对象的删除?
我尝试使用它作为 x86 上某些多线程应用程序的替代品,它似乎表现良好。但我担心由于其他平台的内存架构,此代码中存在未显示的错误。
进一步说明:除了尝试推断内存顺序之外,如何测试线程安全性。
这只是问题的部分答案:
您可以使用libstdc++作为参考(代码重新格式化):
template<> inline bool _Sp_counted_base<_S_atomic>::_M_add_ref_lock_nothrow() noexcept { // Perform lock-free add-if-not-zero operation. _Atomic_word __count = _M_get_use_count(); do { if (__count == 0) return false; // Replace the current counter value with the old value + 1, as // long as it's not changed meanwhile. } while (!__atomic_compare_exchange_n(&_M_use_count, &__count, __count + 1, true, __ATOMIC_ACQ_REL, __ATOMIC_RELAXED)); return true; }
首先,请注意您的循环中有一个毫无意义的
load
操作:
old = blk->strong.load(std::memory_order_relaxed);
当
std::compare_exchange
失败时,无论如何,当前值都会加载到 old
中,因此此加载对您没有任何作用。
第二:
relaxed
命令。无论如何,compare_exchange
都是读取-修改-写入操作,并且您不会对加载它和重试compare_exchange
之间的值执行任何操作,因此任何更强的内存顺序都是无用的。relaxed
对我来说似乎就足够了(MSVC在这里使用relaxed
,尽管libstdc++使用acq_rel
)。我没有信心说acq_rel
是否绝对必要,但你冷用它是为了安全起见。