how to implement std::weak_ptr::lock with atomic operations?

Viewed 135

I recently tried to implement an atomic reference counter in C, so I referred to the implementation of std::shared_ptr in STL, and I am very confused about the implementation of weak_ptr::lock. When executing compared_and_exchange, clang specified memory_order_seq_cst, g++ specified memory_order_acq_rel, and MSVC specified memory_order_relaxed. I think memory_order_relaxed has been enough, since there is no data needed to synchronize if user_count is non-zero. I am not an expert in this area, can anyone provide some advice?

Following are code snippets:

MSVC

    bool _Incref_nz() noexcept { // increment use count if not zero, return true if successful
        auto& _Volatile_uses = reinterpret_cast<volatile long&>(_Uses);
#ifdef _M_CEE_PURE
        long _Count = *_Atomic_address_as<const long>(&_Volatile_uses);
#else
        long _Count = __iso_volatile_load32(reinterpret_cast<volatile int*>(&_Volatile_uses));
#endif
        while (_Count != 0) {
            const long _Old_value = _INTRIN_RELAXED(_InterlockedCompareExchange)(&_Volatile_uses, _Count + 1, _Count);
            if (_Old_value == _Count) {
                return true;
            }

            _Count = _Old_value;
        }

        return false;
    }

clang/libcxx

__shared_weak_count*
__shared_weak_count::lock() noexcept
{
    long object_owners = __libcpp_atomic_load(&__shared_owners_);
    while (object_owners != -1)
    {
        if (__libcpp_atomic_compare_exchange(&__shared_owners_,
                                             &object_owners,
                                             object_owners+1))
            return this;
    }
    return nullptr;
}

gcc/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;
    }
1 Answers

I am trying to answer this question myself.

The standard spec only says that weak_ptr::lock should be executed as an atomic operation, but nothing more about the memory order. So that different threads can invoke directly weak_ptr::lock in parallel without any race condition, and when that happens, different implementations offer different memory_order. But no matter what, all the above implementations are correct.

Related