May 2009 C++ Standards Committee Mailing - Object Lifetimes and Threads
Monday, 18 May 2009
The May 2009 mailing for the C++ Standards Committee was published a couple of weeks ago. This is a minimal mailing between meetings, and only has a small number of papers.
The primary reason I'm mentioning it is because one of the papers
is concurrency related: N2880:
C++ object lifetime interactions with the threads API by
Hans-J. Boehm and Lawrence Crowl. Hans and Lawrence are concerned
about the implications of thread_local
objects with destructors, and
how you can safely clean up threads if you don't call join()
. The
issue arose during discussion of the proposed async()
function, but is generally applicable.
thread_local
variables and detached threads
Suppose you run a function on a thread for which you want the
return value. You might be tempted to use std::packaged_task<>
and std::unique_future<>
for this; after all it's almost exactly what they're designed for:
#include <thread> #include <future> #include <iostream> int find_the_answer_to_LtUaE(); std::unique_future<int> start_deep_thought() { std::packaged_task<int()> task(find_the_answer_to_LtUaE); std::unique_future<int> future=task.get_future(); std::thread t(std::move(task)); t.detach(); return future; } int main() { std::unique_future<int> the_answer=start_deep_thought(); do_stuff(); std::cout<<"The answer="<<the_answer.get()<<std::endl; }
The call to get()
will wait for the task to finish, but not the
thread. If there are no thread_local
variable
this is not a problem — the thread is detached so the library
will clean up the resources assigned to it automatically. If there
are thread_local
variables (used in
find_the_answer_to_LtUaE()
for example), then this
does cause a problem, because their destructors are not
guaranteed to complete when get()
returns. Consequently, the program may exit before the destructors of
the thread_local
variables have completed, and we have a
race condition.
This race condition is particularly problematic if the thread
accesses any objects of static storage duration, such as global
variables. The program is exiting, so these are being destroyed; if
the thread_local
destructors in the still-running thread
access global variables that have already been destroyed then your
program has undefined behaviour.
This isn't the only problem that Hans and Lawrence discuss —
they also discuss problems with thread_local
and threads
that are reused for multiple tasks — but I think it's the most
important issue.
Solutions?
None of the solutions proposed in the paper are ideal. I
particularly dislike the proposed removal of the detach()
member function from std::thread
. If
you can't detach a thread directly then it makes functions like
start_deep_thought()
much harder to write, and people
will find ways to simulate detached threads another way. Of the
options presented, my preferred choice is to allow registration of a
thread termination handler which is run after all
thread_local
objects have been destroyed. This handler
can then be used to set the value on a future or notify a condition
variable. However, it would make start_deep_thought()
more complicated, as std::packaged_task<>
wouldn't automatically make use of this mechanism unless it was
extended to do so — if it did this every time then it would make
it unusable in other contexts.
If anyone has any suggestions on how to handle the issue, please leave them in the comments below and I'll pass them on to the rest of the committee.
Posted by Anthony Williams
[/ cplusplus /] permanent link
Tags: C++0x, C++, standards, concurrency
Stumble It! | Submit to Reddit | Submit to DZone
If you liked this post, why not subscribe to the RSS feed or Follow me on Twitter? You can also subscribe to this blog by email using the form on the left.
Design and Content Copyright © 2005-2024 Just Software Solutions Ltd. All rights reserved. | Privacy Policy
12 Comments
Hopefully I understand your problem description properly...
Why not make it illegal for a thread to be running when the program exits?
I can't think of a case where I would want the thread to run while a program was exiting.
Speaking of detach, is detach still called in the destructor? IMO, this is going to open up a new class of bugs.No, <code>thread::detach</code> is <a href="http://www.justsoftwaresolutions.co.uk/cplusplus/cplusplus-standards-committee-mailing-march-2009.html">not called</a> in <code>thread::~thread</code> anymore.
Removing detach seems a bit heavy handed and I think the repurcussions will surface in additional APIs / objects that folks write.
My personal preference would be to manage the lifetime of a destructible threadpool object that exposed initialization and cleanup callbacks as opposed to forcing folks to always manage the lifetime of individual threads. Others may differ on this of course.
Good news about ~thread(). Also good to know that people smarter than me agreed with me :-)
Hi Sohail,
Making it illegal (which implies undefined behaviour) to have a thread running during program exit doesn't help things. In the absence of thread_local variables with destructors, the example code above is fine as the thread exits as soon as the task finishes. The problem is that destructors for thread_local variables prolong the thread, with no way of identifying when the thread has truly finished other than by calling join(). This makes some potentially useful patterns of usage open to undefined behaviour in the presence of thread_local objects with destructors.Hi Rick,
Managing threads with a thread pool would be nice, but the C++ committee have declared thread pools out of scope for C++0x, so we have to deal with the issues of managing threads manually for now.
Just a try. What about adding explicit function which will destroy all thread local objects?
Hi Dmitriy,
An explicit function that destroys thread_local objects is one of the proposed solutions in N2880. It would still require careful coding where would you put the call in the example above? find_the_answer_to_LtUaE() might not be run on its own thread, so we can't put the call in there. Once packaged_task::operator() has returned we're no better off than now --- the future is ready, so the main thread can resume and start global destruction.
That leaves packaged_task::operator(). In general, this can be run on any thread at any time, so we can't just blindly put the "destroy thread locals" call in there. Therefore, we must pass in a flag either through the constructor or directly to operator() saying "destroy thread locals please". This is untidy in my opinion, but may be better than other options.
But the whole point you were making as I understand it is: * We have a detached thread * Said thread may have thread local variables that have yet to be destructed * We might exit before this happens
"If there are thread_local variables (used in find_the_answer_to_LtUaE() for example), then this does cause a problem, because their destructors are not guaranteed to complete when get() returns. Consequently, the program may exit before the destructors of the thread_local variables have completed, and we have a race condition."
So there are some options: * Before starting destruction of global variables, the library should join() all detached threads * Manually register some de-register functions (no thanks) * Make it a diagnosed error to have a thread running when the program is exiting (my favourite)
I don't see how the third option above does not handle the specific case you were discussing.
"This makes some potentially useful patterns of usage open to undefined behaviour in the presence of thread_local objects with destructors."
What patterns of usage?I agree with Sohail that without a requirement to join all the threads one hardly can find some beautiful and bullet-prof solution. It's a kind of unsolvable problem: you want to not care about thread lifetime, and you want to care about thread lifetime at the same time. And when join is conducted manually, I would prefer: void my_formally_detached_thread() { some_useful_work_here(); std::destroy_thread_local_objects(); notify_my_app_that_I_am_finished(); // substitution for join() }
over: void my_formally_detached_thread() { some_useful_work_here(); std::register_callback_after_destruction_of_thread_local_objects(my_callback); }
void my_callback() { notify_my_app_that_I_am_finished(); // substitution for join() }
Could this be attacked on the future's front? Could the packaged task, for example, register a thread with the future such that the thread that reads the future will automatically join with the task that's responsible for the future? One major disadvantage to this is that I don't believe the current specification for futures have any kind of thread ownership. This approach would also reduce parallelism because the getter would block on the destructors of the setter thread.
Sohail, the problematic usage example for having a diagnosed error if threads are running on exit is exactly the one illustrated in this blog post: the find_the_answer_to_LtUaE() thread is *trying* to shut down, but once it sets the future, it has no way of preventing from exiting first. Similarly, in the spirit of modularity, the main() program has no direct knowledge of that thread and cannot join with it. The committee considered automatically joining all outstanding threads on exit, but decided that the chance of a program hanging indefinitely on exit is near 100%. For example, if a thread is waiting on an event queue, it may be harmlessly left running at the time of exit. To require otherwise would require some kind of cooperative thread termination, which many think is a good idea, but which was nixed for this version of the standard.
I agree that the straight-forward std::destroy_thread_local_objects() call makes Dmitriyy's example simpler than the callback. I can see that in some cases the reverse might be true. We don't have to supply just one solution, but it would be good to have something that worked.
The big issue is that the part of the code that does "notify_my_app_that_I_am_finished()" may be buried inside a class or function that can be called from other places, so you cannot inject code to destroy thread-locals immediately before it, or extract it to a callback. std::packaged_task is actually a case in point --- you cannot easily have it destroy thread-local variables, or pass the thread handle into the returned future as per Pablo's suggestion, since it can be used in cases where it is not being used directly as the thread function.
One option here is to provide parameters to std::packaged_task that indicate this is the desired behaviour, or to provide a std::async function that packages the thread handle into the returned future. Neither option strikes me as ideal, since it limits the options for users, and they can't do the same. We could provide all the options: std::async(), an instrumented std::packaged_task, std::destroy_thread_locals() and std::register_final_callback(), but I'd rather somehow come up with a better alternative.