Thread-safe weak pointers

Started by
13 comments, last by Shannon Barber 9 years, 8 months ago

Hello everybody,

this is my first post here after lurking around for the better part of the last two years, so greetings to all the nice people here smile.png

Onto my actual question: I'm currently developing a small rendering engine for the company I work at (so unfortunately I can't post any code, sorry guys...) which is coming along rather nicely, but one topic that I'm unhappy with is handling of the resources for renderer (models, textures, shaders etc.). I rather dislike throwing around raw pointers, especially to clients of my renderer, so I initially thought it would be a good idea to use some sort of weak pointers. For this, I'm keeping all pointers to the raw resources in an internal array and give clients objects that act like pointers by holding and index into the internal array. Now, my renderer is working multithreaded and both clients as well as the renderer itself may create or destroy resources at any time, which means I have to lock access to the internal array. This turned out to be a noticable performance hit, since I basically lock a mutex every time someone calls operator->() on any of those pointers.

So I thought about what my resource pointers should be able to handle:

  • They should abstract the underlying pointer object and disallow clients to mess with them (no "delete somePtr.Get()" or something like that)
  • They have to handle destroyed resources gracefully: The clients are able to free specific resources at any time they want, which should invalidate all pointers to that resource (which is easy with weak pointers, I can simply check if the object at the index exists or not)
  • They have to be thread-safe or better yet don't care about threads at all
  • Access of the underlying objects has to be fast

I thought about smart pointers, but they're not very well suited for explicit destruction of an underlying object. Also, I don't need reference counting. I also thought about just using an object that encapsulates the raw pointer inside itself, but then I would have no way to find out if the pointer points to something valid or something that has already been destructed.

So if anyone has an idea on how to handle this situation gracefully, I would be happy to know smile.png I also appologise if something similar has been asked already, I did only find information on the usual smart pointers online.

Greetings,

Mortano

Advertisement

Weak pointers (as in std::weak_ptr) are already thread-safe. You do not need explicit locks, instead you convert your weak pointers to shared pointers (ironically by calling a function that's called lock() on them).

What does this do and how does it work, and how is it thread-safe?

Calling lock() on a std::weak_ptr will do one of two things. It either succeeds and returns a std::shared_ptr, or it fails and returns a null pointer. You can check whether you got back a null pointer, so you know for certain, with no possibility of a doubt. If you did get back a null pointer, it means that object had already been destroyed earlier, otherwise the object is there and alive.

For as long as you keep the shared pointer, the object it refers to is guaranteed to exist (it is "locked" in some sense). This is guaranteed to work in a multithreaded environment.

I do something quite similar to this at the moment. It's in very early developmental stages but so far i've not noticed a performance hit from my approach. Have you thought about going lock-free and having a flag on each entry that is altered with compare and swap? It might be that you just need to get the cost of your lock down.

They should abstract the underlying pointer object and disallow clients to mess with them (no "delete somePtr.Get()" or something like that)


This is dangerous thinking. There's nothing I dislike more than code that tries overly hard to force programmer not to do stupid things. You can, for starts, just not hire programmers who do stupid things. Second, you can already do checkin reviews or automatic checkin filters that detect bad practices like calling `operator delete` ever. As further point, consider that a bad programmer can already just call `delete &stack_value` and cause bad things to happen.

If the pointer type _naturally_ should be opaque, great. Otherwise, don't force things.

Sean Middleditch – Game Systems Engineer – Join my team!

Another option if you don't want to expose object through pointer is to user unique resource ids ( could be some for on integral type or whanot ), but that also comes with its share of issues. I also agree with SeanMiddleditch sentiment, one should assume a level of competence for users of your code. There is not need for excessive abstraction, when simply documenting the usage of the interface or whanot would suffice. If its documented that you should not do X and the users still go ahead and do so...see where I"m geting..?

Thanks for the answers guys biggrin.png

std::weak_ptr might just work, I will try this instead of my own crude weak pointer implementation (I guess that's what you get for thinking "who needs STL, I'll just do it all by myself"). The main problem is indeed the cost of the lock, I will also look into the compare-and-swap style approach, thanks for pointing it out Dave!

This is dangerous thinking. There's nothing I dislike more than code that tries overly hard to force programmer not to do stupid things.

Thanks for the honest words, and you're propably right with it too. I just remember reading something like "Write your code so that it is hard to use it incorrectly", which I liked as an approach. But no one would propably do something like delete somePtr.Get() without malicious intent, and then he would only harm himself smile.png

Thanks for the honest words, and you're propably right with it too. I just remember reading something like "Write your code so that it is hard to use it incorrectly", which I liked as an approach. But no one would propably do something like delete somePtr.Get() without malicious intent, and then he would only harm himself


You're probably referring to Effective C++ (3rd ed) Item 18 "Make interfaces easy to use correctly and hard to use incorrectly"?

It's good advice, and I'd argue you're already following it. For example, if you had an implicit T* conversion operator in your class, that would allow people to easily write


delete someSmartPtr;

and it would compile fine. But any decent programmer would look at


delete someSmartPtr.get();

and get worried.

The important word there is "hard", not impossible. If you make the developer think about something they possibly shouldn't be doing instead of doing it accidentally, that's good, but if you stop an expert from doing something he needs to do, he'll just get annoyed.

if you think programming is like sex, you probably haven't done much of either.-------------- - capn_midnight

I think if you need to lock you are having a greater design issue.

Perhaps you have a bigger blob of information known as a game object with contains all the data for it, which means all different parts of the system that migh run in different threads needs accesss to that structure and thus lock it.

The solutions is to identify if you can slice up this bigger game object in separate objects and keep them relevant only to the part of the system that needs it.

For example, the renderer might contain poinsters to stuff like the texture and the model, the logic layer stores the game object name, HP/Mana, hostility level, while the physics has the rigid body and collision mesh etc.

I made a 3 videos of this, they are pretty shitty but should give you the right idea:

So I tried implementing it with std::weak_ptr which makes accessing of the underlying pointer significantly faster, but now copying of the pointers is much slower than before (where I only copied the weak reference index). I'll have to do some more profiling, but it might be that this additional cost is amortized by the gain in access speed. All in all I think your answers have helped me a lot and I also have some more interesting areas where I will look into to see if and how I can optimize further.

Perhaps you have a bigger blob of information known as a game object with contains all the data for it, which means all different parts of the system that migh run in different threads needs accesss to that structure and thus lock it.

In a general game engine that may often be the case, but I'm developing only a renderer, nothing more smile.png Basically only an elaborate abstraction of graphics APIs. Also I'm already at a point where fundamental design changes are not feasible because of too much code that has to change.

It feels a bit weird that you need to copy them so much that it actually matters. Are you certain you are using const references or move semantics where possible?

This topic is closed to new replies.

Advertisement