Jump to content
  • Advertisement
Sign in to follow this  
Erik Rufelt

std::thread arguments and copies

This topic is 1013 days old which is more than the 365 day threshold we allow for new replies. Please post a new topic.

If you intended to correct an error in the post then please contact us.

Recommended Posts

Encountered an interesting scenario, that I'm not sure if there's a guarantee for.

static void DestructionHandler(std::shared_ptr<Object> object) {
 while(!object.unique()) {
  sleep(100 ms);
 }
}

...

void something() {
 std::shared_ptr<Object> object = std::make_shared<Object>();
 std::thread destructionHandler(DestructionHandler, object);
 destructionHandler.detach();
}

Is the loop guaranteed to exit, or could the underlying OS code that calls DestructionHandler have an extra reference to the object?

Is the loop guaranteed to exit, or could the underlying library code that calls DestructionHandler have an extra reference to the object (extra copy of the shared_ptr)?

Edited by Erik Rufelt

Share this post


Link to post
Share on other sites
Advertisement

I'm more concerned with: could it possibly be? :)

Though my question was not properly worded using "OS" when the directly underlying caller is probably not the OS but the library implementation (I guess..).

Share this post


Link to post
Share on other sites

Reading through the standard it seems to be guaranteed, it says that the types of the arguments have to be move-constructible and use what they call "decay copy", so I assume it will have to work at least logically something like copy-constructing to some allocated memory that is passed to the new thread, and then when the library implementation takes over on that thread it will move-construct those objects onto the topmost stack frame in the arguments to the thread handler function.. so I guess it is guaranteed to not hold extra copies.

 

Though I'm not well versed enough in these rules that I completely understand if there is actually a guarantee that this is how it _will_ work and not just how it _must be possible for it to work_...

Edited by Erik Rufelt

Share this post


Link to post
Share on other sites

Maybe I should have asked the inverse question :-)

 

 

Even if the OS implementation of thread parameters is C++, how could it possibly know what to do with arbitrary things passed around via thread parameters?

 

 

There are in fact two important things here, which you've hinted at, but are worth pointing out for clarity: the OS does nothing with the memory you pass it (it can't!) and the library is free to do whatever it likes when handing off from an OS thread entry procedure to the thread proc you gave it.

 

 

I don't intuitively feel like the standard needs to guarantee anything with regards to copies passed between threads, aside from what it always guarantees about copies. But the standards committee does not, for better or worse, have any human-like notion of making intuitive sense, so YMMV.

Share this post


Link to post
Share on other sites


and the library is free to do whatever it likes when handing off from an OS thread entry procedure to the thread proc you gave it

 

That's the main question, if "whatever it likes" includes keeping a copy of the object around for whatever reason, or without reason.

I'm not really concerned with temporary copies as such, just non-destructed copies. I may have been unclear about this.

 

 

 

Another way to write the question:

void ThreadProc(std::shared_ptr<Object> object) {
 object.reset();
 while(true) {
 }
}

void test() {
 std::thread t(ThreadProc, std::make_shared<Object>());
 t.detach();
}

Will the Object whose constructor is called when make_shared creates the object ever have its destructor called?

 

 

Again, not sure if it makes sense to even care.. just simpler if it was guaranteed to be destructed.

If not, objects that I really want to always be destructed even when the thread continues to run have to be passed to the thread in another way than the thread arguments.

 

Seems somewhat senseless for a copy to be kept around somewhere, but that hasn't really stopped anything before :)

Share this post


Link to post
Share on other sites

Ask yourself this: what if the underlying OS is not written in C++?
Even if the OS implementation of thread parameters is C++, how could it possibly know what to do with arbitrary things passed around via thread parameters?

Why is the OS relevant? We're in C++ library land, not OS land.
I can write a C++ wrapper around the Windows/Linux OS threading APIs that respects the C++ object model... I would assume the C++ standard library also does so.

std::thread copies/moves all the constructor arguments to temporary storage, then launches the underlying OS thread, then invokes your callback in that thread (passing the args from the temporary storage).
So I'm not sure, but this may be an infinite loop:
1) something has a shared-ptr
2) std::thread's constructor copies it (now two ref's)
3a) DestructionHandler copies it (now three ref's)
4a) something exits and destructs it's shared-tr (now two ref's)
OR
3b) something exits and destructs it's shared-tr (now one ref)
4b) DestructionHandler copies it (now two ref's)

You can pass the arguments by reference instead, and have the main thread block until DestructionHandler runs far enough to make a value-copy from the reference-argument -- that would eliminate any hidden temporaries. That's actually what I do in my own OS-thread wrapper, so that I can pass objects by value without having to keep extra copies around for longer than necessary. Edited by Hodgman

Share this post


Link to post
Share on other sites
My reasoning was on similar lines of Hodgman, however since a sensible implementation should prefer moving over copying you should never have more than one reference counted anywhere and it should work. If it did not work like that, transferring a std::unique_ptr<> instead of std::shared_ptr<> would be impossible and although I cannot remember ever having done that I'm pretty sure it should be possible. That said, at least the documentation over on cppreference.com is not as clear as it could be and I'm unsure if it is guaranteed behavior. Maybe going to the actual standard would help but I'm currently in no mood to do that digging.

Share this post


Link to post
Share on other sites

Thanks for your replies!
The unique_ptr example is a good one.. that wouldn't work if anything but a move constructor was used obviously.. so depending on template argument handling move-construction for everything on the new thread is probably the only thing that makes sense...

 

1: Either move or copy all args into the allocated temp storage depending on their template types (still on main thread)

1b: Including the ThreadProc (it's treated specifically by the standard, could be a lambda, std::function etc)

2: Start thread

3: (on thread 2) std::move all args from temp storage in a call to the user-defined ThreadProc

So if that's how it always works for all args then exactly one copy will exist on thread 2 and it will be the arguments to ThreadProc on the stack.

If however the lib implementation was done so that (3) did not properly use move-construction for an argument then there could be a problem.

 

Thought of another case that must be kept in mind for this, if using a lambda or function wrapper object, namely how operator () is implemented in respect to its arguments (if the thread proc is not directly operator() )

 

Probably best either way to do like Hodgman says with refs for clearity, or the easiest might be to pass in a unique_ptr<shared_ptr>, to wrap it into something that couldn't possibly be copied anywhere on the way.

Edited by Erik Rufelt

Share this post


Link to post
Share on other sites

The arguments passed to the thread constructor need to be copyable (at least in C++11). So unique_ptr won't work.

 

And, at least in the vs 2013 library implementation, the ref count of the object is 2 when the main thread has released its reference - so unique() will never be true.

Edited by phil_t

Share this post


Link to post
Share on other sites
Sign in to follow this  

  • Advertisement
×

Important Information

By using GameDev.net, you agree to our community Guidelines, Terms of Use, and Privacy Policy.

We are the game development community.

Whether you are an indie, hobbyist, AAA developer, or just trying to learn, GameDev.net is the place for you to learn, share, and connect with the games industry. Learn more About Us or sign up!

Sign me up!