Deutsch   English   Français   Italiano  
<vd3c5a$4sp5$1@dont-email.me>

View for Bookmarking (what is this?)
Look up another Usenet article

Path: ...!news.mixmin.net!eternal-september.org!feeder3.eternal-september.org!news.eternal-september.org!.POSTED!not-for-mail
From: Paavo Helde <eesnimi@osa.pri.ee>
Newsgroups: comp.lang.c++
Subject: Re: Atomic caching of smart pointers
Date: Thu, 26 Sep 2024 13:14:02 +0300
Organization: A noiseless patient Spider
Lines: 198
Message-ID: <vd3c5a$4sp5$1@dont-email.me>
References: <vc7ahq$2akr4$1@dont-email.me> <vc7f5o$2atht$5@dont-email.me>
 <vca82m$32puo$1@dont-email.me> <vca8v5$327mo$1@dont-email.me>
 <vcb5ij$3c3g5$1@dont-email.me> <vcb5r9$3c740$1@dont-email.me>
 <vcb65v$3c73m$1@dont-email.me> <vcbhoq$3epub$1@dont-email.me>
 <vd2sln$2lkc$1@dont-email.me>
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8; format=flowed
Content-Transfer-Encoding: 8bit
Injection-Date: Thu, 26 Sep 2024 12:14:03 +0200 (CEST)
Injection-Info: dont-email.me; posting-host="25af6294e0cdc6ebb8363fbc0b8f79fa";
	logging-data="160549"; mail-complaints-to="abuse@eternal-september.org";	posting-account="U2FsdGVkX18U61Ra1JgKQQkZC5XIAJcudkYHsYdaxTk="
User-Agent: Mozilla Thunderbird
Cancel-Lock: sha1:ZFtMIWw2zYkfrpH9CMqPamixgts=
In-Reply-To: <vd2sln$2lkc$1@dont-email.me>
Content-Language: en-US
Bytes: 8246

On 26.09.2024 08:49, Chris M. Thomasson wrote:
> On 9/17/2024 2:22 AM, Paavo Helde wrote:
>> On 17.09.2024 09:04, Chris M. Thomasson wrote:
>>> On 9/16/2024 10:59 PM, Chris M. Thomasson wrote:
>>>> On 9/16/2024 10:54 PM, Paavo Helde wrote:
>>> [...]
>>>>>>>>> template<typename T>
>>>>>>>>> class CachedAtomicPtr {
>>>>>>>>> public:
>>>>>>>>>      CachedAtomicPtr(): ptr_(nullptr) {}
>>>>>>>>>
>>>>>>>>>      /// Store p in *this if *this is not yet assigned.
>>>>>>>>>      /// Return pointer stored in *this, which can be \a p or not.
>>>>>>>>>      Ptr<T> AssignIfNull(Ptr<T> p) {
>>>>>>>>>          const T* other = nullptr;
>>>>>>>>>          if (ptr_.compare_exchange_weak(other, p.get(), 
>>>>>>>>> std::memory_order_release, std::memory_order_acquire)) {
>>>>>>>>>              p->IncrementRefcount();
>>>>>>>>>              return p;
>>>>>>>>>          } else {
>>>>>>>>>              // wrap in an extra smartptr (increments refcount)
>>>>>>>>>              return Ptr<T>(other);
>>>>>>>>>          }
>>>
>>> ^^^^^^^^^^^^^^^^^^
>>>
>>> Is Ptr<T> an intrusive reference count? I assume it is.
>>
>> Yes. Otherwise I could not generate new smartpointers from bare T*.
>>
>>
>> FYI, here is my current full compilable code together with a test 
>> harness (no relacy, could not get it working, so this just creates a 
>> number of threads which make use of the CachedAtomicPtr objects in 
>> parallel.
>>
>> #include <cstddef>
>> #include <atomic>
>> #include <iostream>
>> #include <stdexcept>
>> #include <deque>
>> #include <mutex>
>> #include <thread>
>> #include <vector>
>>
>> /// debug instrumentation
>> std::atomic<int> gAcount = 0, gBcount = 0, gCASFailureCount = 0;
>> /// program exit code
>> std::atomic<int> exitCode = EXIT_SUCCESS;
>>
>> void Assert(bool x) {
>>      if (!x) {
>>          throw std::logic_error("Assert failed");
>>      }
>> }
>>
>> class RefCountedBase {
>> public:
>>      RefCountedBase(): refcount_(0) {}
>>      RefCountedBase(const RefCountedBase&): refcount_(0) {}
>>      RefCountedBase(RefCountedBase&&) = delete;
>>      RefCountedBase& operator=(const RefCountedBase&) = delete;
>>      RefCountedBase& operator=(RefCountedBase&&) = delete;
>>
>>      void Capture() const noexcept {
>>          ++refcount_;
>>      }
>>      void Release() const noexcept {
>>          if (--refcount_ == 0) {
>>              delete const_cast<RefCountedBase*>(this);
>>          }
>>      }
>>      virtual ~RefCountedBase() {}
>>
>>
>> private:
>>      mutable std::atomic<std::size_t> refcount_;
>> };
>>
>> template<class T>
>> class Ptr {
>> public:
>>      Ptr(): ptr_(nullptr) {}
>>      explicit Ptr(const T* ptr): ptr_(ptr) { if (ptr_) { ptr_- 
>>  >Capture(); } }
>>      Ptr(const Ptr& b): ptr_(b.ptr_) { if (ptr_) { ptr_->Capture(); } }
>>      Ptr(Ptr&& b) noexcept: ptr_(b.ptr_) { b.ptr_ = nullptr; }
>>      ~Ptr() { if (ptr_) { ptr_->Release(); } }
>>      Ptr& operator=(const Ptr& b) {
>>          if (b.ptr_) { b.ptr_->Capture(); }
>>          if (ptr_) { ptr_->Release(); }
>>          ptr_ = b.ptr_;
>>          return *this;
>>      }
>>      Ptr& operator=(Ptr&& b) noexcept {
>>          if (ptr_) { ptr_->Release(); }
>>          ptr_ = b.ptr_;
>>          b.ptr_ = nullptr;
>>          return *this;
>>      }
>>      const T* operator->() const { return ptr_; }
>>      const T& operator*() const { return *ptr_; }
>>      explicit operator bool() const { return ptr_!=nullptr; }
>>      const T* get() const { return ptr_; }
>> private:
>>      mutable const T* ptr_;
>> };
>>
>> template<typename T>
>> class CachedAtomicPtr {
>> public:
>>      CachedAtomicPtr(): ptr_(nullptr) {}
>>      /// Store p in *this if *this is not yet assigned.
>>      /// Return pointer stored in *this, which can be \a p or not.
>>      Ptr<T> AssignIfNull(Ptr<T> p) {
>>          const T* other = nullptr;
>>          if (ptr_.compare_exchange_strong(other, p.get(), 
>> std::memory_order_release, std::memory_order_acquire)) {
>>              p->Capture();
> 
> Only one thread should ever get here, right? It just installed the 
> pointer p.get() into ptr_, right?

Yes, that's the idea. The first thread which manages to install non-null 
pointer will increase the refcount, others will fail and their objects 
will be released when refcounts drop to zero.

> 
> 
>>              return p;
>>          } else {
>>              ++gCASFailureCount;
>>              return Ptr<T>(other);
> 
>>          }
>>      }
>>      Ptr<T> Load() const {
>>          return Ptr<T>(ptr_);
>>      }
> 
> Now this is the crux of an potential issue. Strong thread safety allows 
> for a thread to take a reference even if it does not already own one. 
> This is not allowed in basic thread safety.
> 
> So, for example this scenario needs strong thread safety:
> 
> static atomic_ptr<foo> g_foo(nullptr);
> 
> thread_a()
> {
>      g_foo = new foo();
> }
> 
> thread_b()
> {
>      local_ptr<foo> l_foo = g_foo;
>      if (l_foo) l_foo->bar();
> }
> 
> 
> thread_c()
> {
>      g_foo = nullptr;
> }
> 

In my usage case I do not have thread_c() because nobody is changing the 
pointer any more after it is set. It is kept alive while the 
CachedAtomicPtr object is alive. Load() will increment the refcounter, 
so the pointed B object will stay alive even if CachedAtomicPtr is 
destroyed. My test harness is checking this scenario as well.

> 
> This example does not work with shared_ptr, but should work with 
> atomic<shared_ptr>, it should even be lock-free on archs that support 
========== REMAINDER OF ARTICLE TRUNCATED ==========