Boost logo

Boost :

Subject: Re: [boost] [thread] terminating destructor
From: Andrzej Krzemienski (akrzemi1_at_[hidden])
Date: 2012-10-13 07:59:20


2012/10/13 Rob Stewart <robertstewart_at_[hidden]>

> On Oct 12, 2012, at 4:24 PM, "Vicente J. Botet Escriba" <
> vicente.botet_at_[hidden]> wrote:
>
> > Le 10/10/12 14:56, Andrzej Krzemienski a écrit :
> >> 2012/10/10 Vicente J. Botet Escriba <vicente.botet_at_[hidden]>
> >>
> >>> Le 10/10/12 10:23, Andrzej Krzemienski a écrit :
> >>>
> >>> Hi,
> >>>> I can see in the release notes that in Boost 1.52 boost::thread's
> >>>> destructor calls terminate if joinable, in order to conform to C++11
> >>>> specification. I am not sure if this is the best course of action.
> >>>> My understanding -- form the C++ Committee papers and informal
> >>>> presentations -- is that the reason for introducing a 'terminating
> >>>> destructor' was the lack of thread cancellation/interruption
> >>>> functionality.
> >>>> Thread interruption is supposed to be the preferred behavior for
> thread's
> >>>> destructor. std::thread does not support interruption (for some
> reasons),
> >>>> but boost::thread does (this is already a departure from C++11), so
> >>>> shouldn't the latter prefer to interrupt a joinable thread in the
> >>>> destructor?
> >>>>
> >>> yes this is a possible alternative to the standard behavior. But what
> to
> >>> do after interrupting, joining? What others think? Anthony?
> >>>
> >> My preference would be to join after the interruption. If I remember
> >> correctly, the argument against joining for std::thread is that there
> would
> >> be an unexpected hang upon reaching the end of the scope.
>
> That seems a reasonable argument.
>
> >> The argument
> >> against detaching for std::thread is that the detached thread may be
> >> holding references to automatic variables defined in the scope of the
> >> forking thread that we are now exiting.
>
> That seems like too much nannyism. C++ already has many ways to do similar
> things, so why be so concerned with this one case?
>

So are you saying the thread should detach on scope exit rather than
terminate?

>> I believe that with thread interruption in place the argument against
> >> joining is mitigated, while the argument against detaching still holds.
> >
> > I've though more about your suggestion, and it seems to me that it has
> the same drawbacks as joining directly. The point is that interrupting a
> thread doesn't ensures that the thread will finish soon, as the thread
> could not call any interruption point, so that joining them later could
> take an undefined time.
>
> I agree. Hanging like that during stack unwinding would be painful. At
> least with terminate, you'd realize the problem.
>
> MT requires knowledge and skill. Too much handholding gets in the way and
> encourages naive users to get in over their head.
>
> If someone wants a thread to interrupt and join on destruction, they need
> only put the thread object in another object and have that destructor
> interrupt and join.
>

The approach to Boost libraries' interface I encountered so far is that
they provide the simple interface for the newbies (that does a bit of
nanying) and a more complicate interface for the professionals. To me, the
interruption in boost::thread is like a good default behavior for the
beginners. The professionals would surely not use naked std::thread. I
would propose just the opposite. If you are sure you want your thread
handle to terminate the program in destructor, write your wrapper class
yourself, and terminate there, and use the joining destructor for the
default boost::thread interface (for beginners). I believe that for
beginners, termination may not be of use to identify the source of the
problem (I may be wrong though).

Yes, interrupting does not guarantee that you will not hang, however, it
*mitigates* the hang argument. It puts different balance in the choice
between joining and terminating. Perhaps the choice to terminate is still
the best one.

My problem with terminating destructor is that there is no safe way to use
it other than wrap it in another class that takes a different approach in
the destructor. This makes std::thread (and now boost::thread) like a pair
of operators new and delete, which require that you do not use them
directly, but write some wrapper instead. Well, this might be an acceptable
situation after all. What bothers me is that I have seen many introductions
to using threads in C++ that give such example:

  int work( Operation op, Input i1, Input i2 )
  {
    int ans1
    thread th{ [&]{ans1 = op(i1);} };

    int ans2 = op(i2);
    return ans1 + ans2;
  }

It looks like this code makes sense. But it is wrong. You could argue that
one should start teaching multi-threading with async(), but somehow anyone
chooses to start with thread.

Interrupting and joining destructor would be good for novices. And it
enables the scoped (RAII-like) reasoning about threads. People well
familiar with boost::thread would know never to use it naked. Unless
boost::thread itself should be considered a tool for professionals only;
and novices should be encouraged to use async and futures? std::future does
join without interruption. What does boost::future do? I think it should
interrupt and join (or perhaps detach as per N3451).

Regards,
&rzej


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk