Boost logo

Boost :

From: williamkempf_at_[hidden]
Date: 2001-06-27 09:03:51


--- In boost_at_y..., Beman Dawes <bdawes_at_a...> wrote:
> At 06:10 PM 6/26/2001, williamkempf_at_h... wrote:
>
> >It would simply be this:
> >
> > class thread : boost::noncopyable
> > {
> > public:
> > thread(const detail::threadfunc& func);
> > ~thread();
> >
> > bool is_alive() const;
> > bool join();
> > bool detach();
> >
> > static void join_all(); // This may be pointless!?
> > static void sleep(const xtime& xt);
> > static void yield();
> > };
> >
> >The destructor will call join() unless a call to detach() had been
> >previously made.
> >
> >> Also see if you can think of serious impediments to
> >> implementation.
> >
> >The first impediment is that it's a very common practice to copy
> >the "thread" (actually the descriptor, as you pointed out) in
order
> >to allow some other piece of the program manage the thread. For
> >instance, a thread pool is going to have to store the threads in
some
> >sort of data structure. This design requires all such uses to be
> >dynamically allocated via new, which complicates the management
> >though it can be lessened by immediately placing them in a smart
> >pointer.
>
> This just seems like the usual thing you do in C++; that's what
smart
> pointers are for.

The difference is, most types that are noncopyable rarely need to be
copied. Scope lifetime is normal for them. This type isn't the
same. The norm is for them to be copied and passed out of scope. So
you nearly always must use the smart pointer, which is tedious and
repetitious. If the functionality can be encapsulated to reduce
this, I generally think that you should.

> >The second impediment is that constructing threads in a loop
becomes
> >much more complicated. You either have to (again) create the
threads
> >dynamically via new and manage the lifetime (probably with a smart
> >pointer),
>
> Wouldn't shared_ptr<> work just fine? Am I missing something?

It works (and is what I suggested would be done), but again, it's
tedious and repetitious, especially given the frequency of this idiom
in code.
 
> > or call detach() on all of them. The detach() approach
> >will either require you to not care when the threads end (in fact,
> >they will be unceremoniously terminated when the main thread
exits),
> >or to have join_all() join even detached threads (the latter means
> >that you can only use the detach() method if you know what threads
> >may be running and want to wait on all of them).
>
> All that seems appropriate for some applications? Again I'm
missing why
> this is a problem.

You will rarely no all of the threads running in your application.
For example, a GUI widget that displays an MPEG video may spawn a
thread to display the frames. As the application programmer I
wouldn't be aware of this thread, typically, and I certainly wouldn't
want join_all() to wait on this thread, since it would likely cause a
deadlock.
 
> >With smart pointers you get the same result here as you do with
the
> >thread_desc concept, but the syntax becomes more complicated,
which
> >is going to be a hard sell for some developers.
>
> But you only use a smart pointer when you need more than function
scope
> management, or maybe a variable number. If you had an app that
just used
> two threads, wouldn't you would just instantiate the two threads,
like any
> other object? I think of it like files; if you know you need an
input file
> and an output file you just code something like:
>
> ifstream input("foo.in");
> ofstream output("foo.out");

Threads are not totally like files in this regard. Rarely will
a "thread of execution" (boy I see why you hate the name thread for
the type) be bound to some scope in the creating thread. Again,
think of the MPEG widget above. At first you might think that the
thread's scope being defined by the lifetime of the widget, but the
widget may have start and stop buttons that define the lifetime of
the thread. Rarely will a thread's lifetime fit nicely into a simple
block scope.
 
> However, if their lifetime should be different than function scope,
or you
> need a variable number of files, then you use a smart point. But
only
> then.

The scales differ here, however. Pulling numbers out of thin air
just for demonstration, if files need something other than function
scope 10% of the time, threads need it 90% of the time. This puts a
lot more burden on the user. If the burden can be encapsulated and
taken care of for him, I believe you should do so.
 
Another thing I've thought of over night. This design makes it
impossible to have a self() method, but I think this functionality is
a necessity. Looking at the current minimal interface this isn't
readily obvious since there's only 3 things you can do with a thread,
join(), check is_alive() and compare it to another thread. Joining
on oneself would be an error, and checking to see if your still alive
is pointless. However, checking if your the same as another thread
is a valid thing to do. With out a self() method this is simply
impossible to do, in general. As we define other methods for
threads, such as changing their priority, we'll find it even more
compelling to have a self() method.

Bill Kempf


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