Jump to content

  • Log In with Google      Sign In   
  • Create Account

Interested in a FREE copy of HTML5 game maker Construct 2?

We'll be giving away three Personal Edition licences in next Tuesday's GDNet Direct email newsletter!

Sign up from the right-hand sidebar on our homepage and read Tuesday's newsletter for details!


We're also offering banner ads on our site from just $5! 1. Details HERE. 2. GDNet+ Subscriptions HERE. 3. Ad upload HERE.


Thread-safe weak pointers


Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.

  • You cannot reply to this topic
16 replies to this topic

#1 Mortano   Members   -  Reputation: 244

Like
0Likes
Like

Posted 07 August 2014 - 05:32 AM

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



Sponsor:

#2 samoth   Crossbones+   -  Reputation: 4912

Like
7Likes
Like

Posted 07 August 2014 - 06:34 AM

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.



#3 Dave   Members   -  Reputation: 1520

Like
1Likes
Like

Posted 07 August 2014 - 09:51 AM

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.



#4 SeanMiddleditch   Members   -  Reputation: 6347

Like
5Likes
Like

Posted 07 August 2014 - 11:23 AM

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.

#5 cgrant   Members   -  Reputation: 692

Like
2Likes
Like

Posted 07 August 2014 - 12:04 PM

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..?



#6 Mortano   Members   -  Reputation: 244

Like
1Likes
Like

Posted 07 August 2014 - 01:08 PM

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



#7 ChaosEngine   Crossbones+   -  Reputation: 2435

Like
2Likes
Like

Posted 07 August 2014 - 10:53 PM

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

#8 flodihn   Members   -  Reputation: 253

Like
3Likes
Like

Posted 08 August 2014 - 01:55 AM

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:



#9 Mortano   Members   -  Reputation: 244

Like
0Likes
Like

Posted 08 August 2014 - 07:37 AM

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.



#10 BitMaster   Crossbones+   -  Reputation: 4227

Like
1Likes
Like

Posted 08 August 2014 - 09:57 AM

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?



#11 Andy Gainey   Members   -  Reputation: 2043

Like
3Likes
Like

Posted 08 August 2014 - 11:36 AM

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)


This right here is what I would identify as your biggest hurdle.  The fact that threads are capable of assuming very very little regarding the lifetime of any resources they are using makes things incredibly difficult.  It forces the code to protect itself constantly, which brings with it all sorts of inefficiencies.  I think this is the concern that flodihn and BitMaster are expressing too.

 

If you can think of a way to make it safe for threads to make certain assumptions about resource lifetimes and states, that could go a long way.  For example, take any bit of code that frees a resource at a moment that might seem arbitrary to another thread.  Then, instead of freeing the resource, add the resource to a set of resources that need to be freed.  Then, at an appropriate time during the game loop, perhaps, ensure that all other threads are finished or are irrelevant, and then free all of the resources in the to-be-freed set.  This enables all other threads to assume that those resources will always remain valid during the entire time a particular task is being processed.  It might need to check resource validity at the very beginning of the task, but nowhere else.  At that stage, once you know you have a valid resource, you can simply pass references or raw pointers around for any processing that will only work with the reference for the duration of the task at hand.  No need to worry about smart pointers, reference counting, invalidity checks, and especially thread synchronization concerns such as mutexes or atomics, which are often buried inside the details of a thread-safe smart pointer implementation.



"We should have a great fewer disputes in the world if words were taken for what they are, the signs of our ideas only, and not for things themselves." - John Locke

#12 Mortano   Members   -  Reputation: 244

Like
0Likes
Like

Posted 09 August 2014 - 02:06 AM

Ok maybe a little more info on what I'm acutally doing with the renderer: My company is making CAD software, not games (and I apologize for not pointing that our earlier). I figured there are a lot of similarities at least from the graphics point of view to a game engine, however the one big difference is that the client application (the one using my renderer) is not running in realtime. The principle is more like "Render a frame when something changes or the mouse moves". This makes it so hard for me to assume anything about the lifetime of my resources. There is no fixed point at which the application thread is guaranteed not to do anything, so I'm basically assuming that they can access my resources at any time.

So @Andy Gainey, even though what you say sounds very reasonable to me, I'm afraid I can't make any assumptions for the calling application...

However, it seems as if the std::weak_ptrs are handling the situation quite nicely. We are currently in the process of implementing my renderer into our existing software, so I was always profiling the client application and my renderer together. Turns out the performance hit of excessive copying came from the client application not passing my resource pointers around as const references. Once I changed this at some places, the cost of copying seemed like less of an issue since there simply aren't that much copies anymore. 

 

To wrap things up: I think Andys way of thinking would solve all of my problems, but std::weak_ptr works well enough that the little performance overhead is neglectable compared to other things like OpenGL driver overhead.



#13 Jason Z   Crossbones+   -  Reputation: 5154

Like
1Likes
Like

Posted 09 August 2014 - 06:17 AM

It's good that you found a solution to your problem, but I also agree with Andy.  When you say that you have to handle when a resource may or may not be available, then that means you need to check if it is there every time you use it.  In the case of a weak_ptr, you are just wrapping the checking part into the smart pointer - but you are still checking *every time you use it*.

 

Is that what you really want?  If you just put a simple resource manager layer in between your client code and your renderer, then you can easily maintain the lifetime of the resources for the duration that they are actually used.  The resource manager can pass out index based references to resources, with a method called getResource.  That can contain a simple count of the number of times the method has been called for each resource.  When code says it is done with the resource, it can let the resource manager know with releaseResource, which decrements the count.  Once you know that all outstanding references have been released, then you can actually delete the resource. 

 

This follows the advice above to make it easy to use right, but you have to try to do it wrong.  It makes the references simple objects (just integers) which are lightweight, reduces the need to constantly check for resource existence, and nobody can directly delete the resource.  That seems like a pretty good way to achieve what you are trying to do!



#14 wodinoneeye   Members   -  Reputation: 856

Like
2Likes
Like

Posted 11 August 2014 - 09:15 PM

How large are the data sets this CAD system processes and/or that it needs such dynamic usage of data?

 

Memory is huge these days and even if you have to load by parts for specific views (LOD etc...) do the assets involved change (roll in and out of memory)  that constantly ??  

  (multiple clients allowing modding of data another client might be overviewing ?)

 

If it is NOT that dynamic, then shouldnt it be adaquate  to have a request once thru an asset access interface (locks/etc  only there) and then simple pointers with DESIGNED assurance that when the asset is loaded it *stays* loaded, until overtly released.   Even hierarchical requests (objects with sub objects) still is simpler (you just TELL the user that someone (multiple users ?...) has locked an asset  (read or write) and let THEM (or higher levels of the CAD system) handle what to do about it (notifications,logging if batch processing...)

 

You might be doing something much more dynamic, but even then there is a limit to how dynamic and you shouldnt have to be saddled   with more complexity (or performance hits)when its not needed.


--------------------------------------------Ratings are Opinion, not Fact

#15 samoth   Crossbones+   -  Reputation: 4912

Like
1Likes
Like

Posted 12 August 2014 - 06:11 AM


If it is NOT that dynamic, then shouldnt it be adaquate to have a request once thru an asset access interface (locks/etc only there) and then simple pointers with DESIGNED assurance that when the asset is loaded it *stays* loaded, until overtly released.
+1 this.

 

In a game, assets come and go all the time. It is somewhat of a necessity since the game must run on a low-spec computer (or console) too, and assets are much greater than what will fit in RAM.

 

In a CAD program, you draw the same stuff all the time (well, mostly, you do of course make modifications, that's the whole point of the program). All objects need to be there anyway, and they don't just go away, unless the user explicitly does something (e.g. deletes an object). The computer a CAD system runs on can more or less be trusted to be a machine with a big graphics card and a lot of RAM, so keeping around some more stuff doesn't matter that much.

Even when the user clicks on the "textured/wireframe" toggle button (hey, no more textures needed!), you do not really want to throw away all textures . It doesn't make sense, the user might toggle texturing on again, and it isn't acceptable to wait 10 seconds for reloading everything from disk. Nor will you delete a group of meshes (or swap them out) because the user clicks the "hide layer" button. They need to be instantly back if the user clicks again.

 

So, it's pretty safe to assume that unless "something particular happens" an object is neither created nor destroyed. So, it is pretty well-defined what exists at all times.



#16 wodinoneeye   Members   -  Reputation: 856

Like
0Likes
Like

Posted 12 August 2014 - 10:12 PM

Another thing I should have thought of  : Dont modern CAD syetms have 'UNDO chains',  which require keeping any changes to assets (in memory) until they are 'SAVED' and even then better ones would have mod/diff/change-control  that is on disk to allow extended UNDOs (like source control)

 

If it IS a mutli user CAD application then that whole issue of check in-check out of the operating data starts to expand the complexity (and in that case for any 'review'/Overview visualization of larger data sets will look at/use "sync'd" data even while someone else might be working on changes.)


--------------------------------------------Ratings are Opinion, not Fact

#17 Shannon Barber   Moderators   -  Reputation: 1383

Like
0Likes
Like

Posted 16 August 2014 - 11:22 AM

Don't delete resources, instead "logically delete them" so you are not constantly memory thrashing that array.

 

Clean-up on major events like saving.


- The trade-off between price and quality does not exist in Japan. Rather, the idea that high quality brings on cost reduction is widely accepted.-- Tajima & Matsubara




Old topic!
Guest, the last post of this topic is over 60 days old and at this point you may not reply in this topic. If you wish to continue this conversation start a new topic.



PARTNERS