Boost logo

Boost :

Subject: Re: [boost] [thread] terminating destructor
From: Vicente J. Botet Escriba (vicente.botet_at_[hidden])
Date: 2012-10-20 05:56:27


Le 13/10/12 14:40, Vicente J. Botet Escriba a écrit :
> Le 13/10/12 13:59, Andrzej Krzemienski a écrit :
>> 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.
> I would really prefer if Boost.Thread behaves like std::threads as
> much as possible so that moving from one to the other don't introduce
> many surprises.
> I don't know yet if this is doable but I would like that the
> interruption mechanism is defined on top of the design of
> std::threads, that is non cancelable threads, so that the user don't
> pay for what he don't use. There is already a feature request to
> provide a condition variable that is as efficient as the user could
> have using the pthread interface.
>> 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.
> Well, I guess the Boost.thread documentation could help on this sense,
> but I'm not a big writer. I will see however what I can do.
>>
>> Interrupting and joining destructor would be good for novices. And it
>> enables the scoped (RAII-like) reasoning about threads.
> I don't think this default behavior is the good one, even for
> beginners. Of course all this is quite subjective.
>
What do you think of adding a thread_guard class that could interrupt
and join on the destructor?
Example taken from "C++ Concurrency in Action" from A. Williams

#include <thread>
class thread_guard
{
     std::thread& t;
public:
     explicit thread_guard(std::thread& t_):
         t(t_)
     {}
     ~thread_guard()
     {
         if(t.joinable())
         {
             t.join();
         }
     }
     thread_guard(thread_guard const&)=delete;
     thread_guard& operator=(thread_guard const&)=delete;
};

void do_something(int& i)
{
     ++i;
}

struct func
{
     int& i;
     func(int& i_):i(i_){}
     void operator()()
     {
         for(unsigned j=0;j<1000000;++j)
         {
             do_something(i);
         }
     }
};

void do_something_in_current_thread()
{}

void f()
{
     int some_local_state;
     func my_func(some_local_state);
     std::thread t(my_func);
     thread_guard g(t);
     do_something_in_current_thread();
}

The book describes also a scoped_thread that do something similar

#include <thread>
#include <utility>

class scoped_thread
{
     std::thread t;
public:
     explicit scoped_thread(std::thread t_):
         t(std::move(t_))
     {
         if(!t.joinable())
             throw std::logic_error("No thread");
     }
     ~scoped_thread()
     {
         t.join();
     }
     scoped_thread(scoped_thread const&)=delete;
     scoped_thread& operator=(scoped_thread const&)=delete;
};

void f()
{
     int some_local_state;
     scoped_thread t(std::thread(func(some_local_state)));
     do_something_in_current_thread();
}

Would something like these simple classes respond to your needs?

Best,
Vicente


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