Google Groups no longer supports new Usenet posts or subscriptions. Historical content remains viewable.
Dismiss

std::experimental::atomic_shared_ptr

68 views
Skip to first unread message

bitrex

unread,
May 6, 2017, 10:57:21 AM5/6/17
to
Two questions here:

First is, how do I access this template? I'm using GCC 5.4 with C++11, I
believe the documentation says I need to include the header
<experimental/atomic> but I don't seem to have that anywhere in my
installation.

Second, if I have one thread which contains a vector of ordinary
weak_ptrs, which are dereferenced only to read data from the resource,
generated from a vector of shared_ptrs in another thread, which are used
to both read from and write to the resource, will making the shared_ptrs
in the second thread atomic_shared_ptrs ensure safety from races without
modification to the first thread's pointer type?

Marcel Mueller

unread,
May 6, 2017, 11:57:43 AM5/6/17
to
On 06.05.17 16.57, bitrex wrote:
> First is, how do I access this template? I'm using GCC 5.4 with C++11, I
> believe the documentation says I need to include the header
> <experimental/atomic> but I don't seem to have that anywhere in my
> installation.

Probably these experimental files are not part of the usual standard
packages. You need to download and install them separately.

> Second, if I have one thread which contains a vector of ordinary
> weak_ptrs, which are dereferenced only to read data from the resource,
> generated from a vector of shared_ptrs in another thread, which are used
> to both read from and write to the resource, will making the shared_ptrs
> in the second thread atomic_shared_ptrs ensure safety from races without
> modification to the first thread's pointer type?

You can be pretty sure that the old weak_ptr/shared_ptr and the new
atomic pointers are not compatible in any way. It is a distinct type,
but you can convert it to shared_ptr.

So the usage pattern should always be like

atomic_shared_ptr<T> global;

// some thread
shared_ptr<T> local = global;
// work with local

The global, strongly thread safe atomic_shared_ptr are not
dereferencable at all since they can change at any time making the just
received object invalid.


BTW: I ended up by writing my own atomic intrusive pointers years ago,
additionally providing strong thread safety for global pointers - this
will be addressed by atomic_shared_ptr as well. But I still think the
concept of intrusive pointers is superior to shared_ptr.


Marcel

bitrex

unread,
May 6, 2017, 12:48:27 PM5/6/17
to
Thanks. I'm working on my own graphics/game engine (OpenGL under the
hood) and I'm starting to think trying to use these experimental atomic
pointers is not really the best approach to my problem.

I have an object which holds a vector of weak_ptrs which handles the
screen refresh, looping through and calling the underlying display
object's "render" method each redraw. But the underlying objects can be
deleted at any time from the logic, which is why the visualizer is
holding weak_ptrs.

I think it's probably just easier to wrap the few places where the
display object's fields are written to in a regular lock or mutex.

Marcel Mueller

unread,
May 6, 2017, 4:09:49 PM5/6/17
to
On 06.05.17 18.48, bitrex wrote:
> Thanks. I'm working on my own graphics/game engine (OpenGL under the
> hood) and I'm starting to think trying to use these experimental atomic
> pointers is not really the best approach to my problem.
>
> I have an object which holds a vector of weak_ptrs which handles the
> screen refresh, looping through and calling the underlying display
> object's "render" method each redraw. But the underlying objects can be
> deleted at any time from the logic, which is why the visualizer is
> holding weak_ptrs.
>
> I think it's probably just easier to wrap the few places where the
> display object's fields are written to in a regular lock or mutex.

If you use weak_ptr::lock() there is no need for further
synchronization. You might additionally check the unique() function of
returned shared_ptr. If it is unique then you are the only one that has
a reference which implies that the object is no longer needed and
probably needs no screen redraw. You can safely discard it.


Marcel

bitrex

unread,
May 6, 2017, 6:49:13 PM5/6/17
to
On 05/06/2017 04:09 PM, Marcel Mueller wrote:

> If you use weak_ptr::lock() there is no need for further
> synchronization. You might additionally check the unique() function of
> returned shared_ptr. If it is unique then you are the only one that has
> a reference which implies that the object is no longer needed and
> probably needs no screen redraw. You can safely discard it.
>
>
> Marcel

Thank you, good to know! I have been using the lock in the render
thread, so it seems it must be something else other than a
synchronization issue that's causing my code to segfault... ;-)

bitrex

unread,
May 6, 2017, 8:01:06 PM5/6/17
to
On 05/06/2017 04:09 PM, Marcel Mueller wrote:
Ok, so I traced down the segfault problem in my implementation to this
section of code that uses software to render some "motion trails" on 2D
sprites:

void update() override
{

auto obj_ptr = _effect_object_buf.back().get();

_effect_object_buf.push_front(std::move(_effect_object_buf.back()));
_effect_object_buf.pop_back();
new (obj_ptr) DisplayObject2D(*_effect_target);


etc.

_effect_object_buf is a boost::circular_buffer holding shared pointers
that are instantiated with objects, and then weak_pointers are handed
off to the visualizer thread at startup. It just holds copies of the
object from further back in time with its alpha transparency reduced to
represent the "trail", and when one fades out completely it's pushed to
the front and reassigned to a copy of where the object is now.

It works fine in practice and looks pretty good, for a random amount of
time until the code segfaults, or throws a "pure virtual method called,
terminate called without an active exception" error. Appears doing this
weird thing might not be thread-safe?

The report Valgrind is giving on the crash is that I'm performing
invalid reads on blocks that are already free-d.

Marcel Mueller

unread,
May 7, 2017, 2:29:33 AM5/7/17
to
On 07.05.17 02.00, bitrex wrote:
> void update() override
> {
>
> auto obj_ptr = _effect_object_buf.back().get();

Err, _effect_object_buf is a container of shared_ptr?
Then obj_ptr here is a raw pointer, not taking the ownership of the
object. Neither holding the storage behind it.

Rule of thumb: *never use raw pointers* expecially not in conjunction
with smart pointers.

> _effect_object_buf.push_front(std::move(_effect_object_buf.back()));
> _effect_object_buf.pop_back();

Is the mutable access to _effect_object_buf synchronized or single
threaded? Including readers?
(Btw. be aware of priority inversion if readers and writers do not
operate at the same scheduling priority.)

And why do you put the objects in the circular buffer from back to front
instead of just cycling with an iterator?

> new (obj_ptr) DisplayObject2D(*_effect_target);

Who destroyed the old object at old_ptr before this placement new?

Can no one else access the object behind obj_ptr which is still an entry
in _effect_object_buf at this point?


> The report Valgrind is giving on the crash is that I'm performing
> invalid reads on blocks that are already free-d.

Probably. This explains the pure function exception, but a race with
placement new could result in a similar problem.


Marcel

woodb...@gmail.com

unread,
May 8, 2017, 2:15:29 PM5/8/17
to
On Sunday, May 7, 2017 at 1:29:33 AM UTC-5, Marcel Mueller wrote:
> On 07.05.17 02.00, bitrex wrote:
> > void update() override
> > {
> >
> > auto obj_ptr = _effect_object_buf.back().get();
>
> Err, _effect_object_buf is a container of shared_ptr?
> Then obj_ptr here is a raw pointer, not taking the ownership of the
> object. Neither holding the storage behind it.
>
> Rule of thumb: *never use raw pointers* expecially not in conjunction
> with smart pointers.
>

I use raw pointers in my library code (onwards directory) and
am not aware of what would be better.
https://github.com/Ebenezer-group/onwards


Brian
Ebenezer Enterprises
http://webEbenezer.net

bitrex

unread,
May 9, 2017, 11:06:19 AM5/9/17
to
On 05/07/2017 02:29 AM, Marcel Mueller wrote:

> Who destroyed the old object at old_ptr before this placement new?
>
> Can no one else access the object behind obj_ptr which is still an entry
> in _effect_object_buf at this point?
>
>
>> The report Valgrind is giving on the crash is that I'm performing
>> invalid reads on blocks that are already free-d.
>
> Probably. This explains the pure function exception, but a race with
> placement new could result in a similar problem.
>
>
> Marcel

Thanks, the reason for the segfault appears to be indeed that I was,
like a dummy, not destructing the old objects prior to placing the new ones.

The reason I'm recycling the pointers from back to front is basically
because those addresses have already been allocated and "handed off" to
the display thread in the form of weak_ptrs at startup prior to
initialization of the logic thread. This buffer of "effect objects" is
contained within another object that handles the logic of the main
object to which the motion blur effect is being applied; a pointer to
its sprite object is also handed off to the display thread in the same
fashion.

I don't think I can dynamically allocate new blocks of the buffer with
new addresses without having to implement some observer for the display
thread that watches every object for whenever anything changes.

If the containing object is destroyed then the use count of all those
shared_ptrs drops to zero, and the display thread can simply check that
to dump them from its list of active objects on the display. If I then
want to resurrect Super Mario and make him appear back on the screen
then yeah I will need to have the display-thread re-acquire a new set of
pointers, but I can have it simply observe the logic thread as a whole
for when things change, rather than every individual type of object.
That's the idea.


If I perform the operations in this sequence:

auto obj_ptr = _effect_object_buf.back().get();
obj_ptr->~DisplayObject2D();
new (obj_ptr) DisplayObject2D(*_effect_target);
_effect_object_buf.push_front(std::move(_effect_object_buf.back()));
_effect_object_buf.pop_back();

everything seems to work fine with no lock on these operations required,
only the weak_ptr lock when the display thread accesses them. It hasn't
happened in testing at least, but I'm not experienced enough with
threading to know if there's actually a race hazard doing it this way,
or not.

bitrex

unread,
May 9, 2017, 11:10:05 AM5/9/17
to
On 05/09/2017 11:05 AM, bitrex wrote:
> On 05/07/2017 02:29 AM, Marcel Mueller wrote:
>
>> Who destroyed the old object at old_ptr before this placement new?
>>
>> Can no one else access the object behind obj_ptr which is still an entry
>> in _effect_object_buf at this point?
>>
>>
>>> The report Valgrind is giving on the crash is that I'm performing
>>> invalid reads on blocks that are already free-d.
>>
>> Probably. This explains the pure function exception, but a race with
>> placement new could result in a similar problem.
>>
>>
>> Marcel
>
> Thanks, the reason for the segfault appears to be indeed that I was,
> like a dummy, not destructing the old objects prior to placing the new
> ones.

I think the effect looks pretty nice for a software-generated effect,
though! Someday I'll learn more about OpenGL shaders and stuff...

http://imgur.com/a/TmFyc

Chris M. Thomasson

unread,
May 11, 2017, 5:23:19 PM5/11/17
to
Marcel Mueller posts in this thread are great, feel no need to jump in.

However, wrt shaders, check this crap out:

http://webpages.charter.net/appcore/fractal/webglx/ct_complex_field.html
(has point and click zoom, unfortunately has no unzoom...)

Shaders are wonderful tools to work with.
0 new messages