C++ 原子为shared_ptr 实现正确的内存顺序和线程防护

问题描述 投票:0回答:1

学习

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 上某些多线程应用程序的替代品,它似乎表现良好。但我担心由于其他平台的内存架构,此代码中存在未显示的错误。

进一步说明:除了尝试推断内存顺序之外,如何测试线程安全性。

c++ multithreading smart-pointers stdatomic compare-and-swap
1个回答
0
投票

这只是问题的部分答案:

您可以使用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
    是否绝对必要,但你冷用它是为了安全起见。
© www.soinside.com 2019 - 2024. All rights reserved.