Path: ...!weretis.net!feeder8.news.weretis.net!eternal-september.org!feeder3.eternal-september.org!news.eternal-september.org!.POSTED!not-for-mail From: Paavo Helde Newsgroups: comp.lang.c++ Subject: Re: Atomic caching of smart pointers Date: Tue, 17 Sep 2024 08:54:28 +0300 Organization: A noiseless patient Spider Lines: 73 Message-ID: References: MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8; format=flowed Content-Transfer-Encoding: 8bit Injection-Date: Tue, 17 Sep 2024 07:54:28 +0200 (CEST) Injection-Info: dont-email.me; posting-host="8634390b878b76fe06bd0d2d4efc76cb"; logging-data="3542533"; mail-complaints-to="abuse@eternal-september.org"; posting-account="U2FsdGVkX18/kstBsuAEnumZ+tfXlIDzhR/N8168JKA=" User-Agent: Mozilla Thunderbird Cancel-Lock: sha1:SZ/h1qEzhH0IKC0yHVXJExiTq5k= Content-Language: en-US In-Reply-To: Bytes: 4343 On 17.09.2024 00:46, Chris M. Thomasson wrote: > On 9/16/2024 2:31 PM, Paavo Helde wrote: >> On 15.09.2024 23:13, Chris M. Thomasson wrote: >>> On 9/15/2024 11:54 AM, Paavo Helde wrote: >>>> >>>> I am thinking of developing some lock-free data structures for >>>> better scaling on multi-core hardware and avoiding potential >>>> deadlocks. In particular, I have got a lot of classes which are >>>> mostly immutable after construction, except for some cached data >>>> members which are calculated on demand only, then stored in the >>>> object for later use. >>>> >>>> Caching single numeric values is easy. However, some cached data is >>>> large and accessed via a std::shared_ptr type refcounted >>>> smartpointers. Updating such a smartpointer in a thread-shared >>>> object is a bit more tricky. There is a std::atomic >>>> in C+ +20, but I wonder if I can do a bit better by providing my own >>>> implementation which uses CAS on a single pointer (instead of DCAS >>>> with additional data fields or other trickery). >>>> >>>> This is assuming that >>>> >>>> a) the cached value will not change any more after assigned, and >>>> will stay intact until the containing object destruction; >>>> >>>> b) it's ok if multiple threads calculate the value at the same time; >>>> the first one stored will be the one which gets used. >>>> >>>> My current prototype code is as follows (Ptr is similar to >>>> std::shared_ptr, but is using an internal atomic refcounter; >>>> using an internal counter allows me to generate additional >>>> smartpointers from a raw pointer). >>>> >>>> template >>>> 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 AssignIfNull(Ptr 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(other); >>>>          } >>>>      } > > So as long as CachedAtomicPtr is alive, the cached pointer, the one that > gets installed in your AssignIfNull function, will be alive? Correct. The `p->IncrementRefcount();` line will keep the assigned T object alive, regardless of how many other smartpointers there are pointing to it in any threads. The refcount is decremented in ~CachedAtomicPtr(). > Sorry if my > question sounds stupid or something. Get trying to get a handle on your > usage pattern. Also, the first pointer installed in CachedAtomicPtr will > remain that way for the entire duration of the lifetime of said > CachedAtomicPtr instance? Correct. Otherwise I would need a mutex, DCAS or some other trickery for avoiding races. Thanks for the answer! Paavo