Boost logo

Boost :

From: Yitzhak Sapir (yitzhaks_at_[hidden])
Date: 2002-08-07 11:24:19


> -----Original Message-----
> From: Eric D Crahen [mailto:crahen_at_[hidden]]
> On Aug 7 2002 Yitzhak Sapir wrote:
>
> I think its a matter of design. If you are going to subclass a thread
> class, and write it in such a way that you need to be sure
> the thread is
> not destroyed before it has exited, you can build in some
> mechanism to do
> that.
>
> // psuedo code
> class Worker : public Thread {

In my message I was referring to the oo_thread implemenation, specifically, since that is all I had to work with. I have much lesser comments for your design because it seems safe, just seems to me to simply be a higher level library around something like boost::threads providing ready made objects like Task and Worker.

> If you wanted to, you could make it more automatic and
> incorporate those
> things into the destructor. Maybe you make stop a part of
> join() in your
> subclass. Maybe you make the thread itself able to detect
> when there is no
> more work for it to do w/o a message from another thread telling it to
> stop; its up to the user how they want to interact with the class.

This would have to occur in the destructor of the class implementing Run(), not in the interface class declaring pure virtual Run(), correct? I was talking more about the fact that you can't write such a pure virtual Run() type thread wrapper base class (which wrapped both the thread data and the execution data).

> Because of the design in the example, I'd consider it a mistake to not
> join this thread before trying to destroy it. The thread
> object itself has
> become a piece of shared data so you'd want to synchronize
> operations like
> destruction.

In the oo_thread example, if you don't join() you get a race condition. If you do join() you get multiple joins() in a row giving undefined behavior in Boost.Threads model. In any case the construction and destruction of such a joint thread-execution-data-class has to be done explictly, whether you call them start/stop, create/join, initialize/shutdown, etc. The only thing the "OO" class provides you is an abstract Run() method. But Boost.Threads works with classes having pure virtual run methods too, and very easily:

struct ITask
{
virtual void operator()() = 0;
};

class SomeTask : public ITask
{
public:
  SomeTask() : m_ptask(new boost::thread(boost::ref(*this))) {}
  ~SomeTask() { /* signal task to finish */ m_ptask->join(); m_ptask.reset(); }
  void operator()()
  {
     //implement your task
  }

private:

 // last member accessed by operator() goes here. declarations after this point may not be used by operator().
 boost::scoped_ptr<boost::thread> m_ptask;
}

The main thing the wrapper class seems to be doing is to wrap "m_ptask(new boost::thread(boost::ref(*this)))" and "m_ptask->join(); m_ptask.reset()" in more readable names but to me that seems minor, since these are very short expressions to remember.

> > I'm not making any such assumption. (So how would you
> solve the basic
> > problem without making this assumption -- that the user
> calls join() in
> > the most inherited class?)
> > Only if you allow it to continue running. (How would you prevent it
> > from running? Remember not to make the assumption that the
> user calls join()
> > in the most inherited class.)
>
> There are other approaches that you can take if you want to create
> detached or daemon threads that might continue to run after a thread
> object has been destroyed (because you aren't requiring a
> user to join the thread)

No, the question as I understood it was more about creating a thread object that represented both a thread and provided a pure virtual run() method. If you separate the two (the thread class and the running function class), your running function class is very much equivalent to the functor that Boost.Threads gets, so the two solutions seem to me to be equal in power, but Boost.Threads gives slightly more freedom in that you don't have to follow any "inherit from this class" rules.

> You have to choose what is going to be best for you design.
> If you want to
> destroy the thread object, but leave the thread running its probably a
> better choice to do something like this last example. If you
> don't have a
> problem join()ing the thread you extend, then the first example is
> probably more fitting. In this last example you still may have other
> reasons to extend the thread. A common thing I see people do
> who have been
> using my library is the extend Thread to create NamedThread
> or something
> similar to add some debugging information to a log, or things
> similar to
> that.

Boost.Threads' model lets you take any function, not just one belonging to a class that inherited from an hierarchy. So it lets you more freedom in the design. If you want to add debugging logs you could do it using a logger functor, and a thread_wrapper:

class logger {
public:
  logger(const boost::function0<void>& a_function) : m_function(a_function) {}
  void operator()()
  {
    /* do init logging */;
    try {
     m_function();
    } catch(...) {
      /* do exception logging*/;
      return;
    }
    /* do exit logging */
  }
};

boost::thread* logger_thread(const boost::function0<void>& a_function)
{
   return new boost::thread(logger(a_function));
}

I don't think this is that much more code than the code you'd have to make for a NamedThread. After all, it's just 20 lines in all...
 
> Task based designs are nice for when you have task that
> really don't need
> to be coupled to a thread. Designs extending a Thread and
> binding the task
> directly to a particular thread object are more suited for
> something that
> you will probably want to run a long time. For instance, in a
> webserver,
> why not extend a thread and put the code that listens for incoming
> connections right in the run method & require your server thread to
> join()ed before its destroyed.

Overall, I don't disagree with your approach. I think the two approaches are equally powerful, with Boost.Threads giving more freedom in the design, and yours being a slightly higher level library that offers less freedom, but more ease of use and more higher level concepts. So far, achieving those higher level concepts with Boost.Threads hasn't been that hard for me, and I'd rather not use two separate libraries to do the same thing, so I opt for the library that gives me more freedom (and of which I learned first). I do use concepts such as you mentioned, so it might be a good idea to have a higher-level library that offered them, above the Boost.Threads library.

> BTW, does anyone know what the proper title is to use to keep messages
> with the right thread (mail list variety)? That '[boost]'
> that gets tossed
> into the title somewhere along the line makes it a little confusing.

Yes, it does. I can't ever sort my inbox by threads.

Yitzhak


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