From: Yitzhak Sapir (yitzhaks_at_[hidden])
Date: 2002-08-06 12:35:50
> -----Original Message-----
> From: Pete Becker [mailto:petebecker_at_[hidden]]
> Sent: Tuesday, August 06, 2002 5:29 PM
> To: boost_at_[hidden]; boost_at_[hidden]
> Subject: RE: [boost] Re: Re: Threads & Exceptions
> At 05:57 PM 8/6/2002 +0200, Yitzhak Sapir wrote:
> >One of the things I like about the Boost.Threads model which
> does not have
> >a virtual run() function is that it makes you think about
> bugs like what I
> >think is a bug in the above code, where run(), a virtual
> function could
> >continue running and accessing the descendant of oo_thread after the
> >destructor of the oo_thread's descendant has been called,
> but before pt
> >has been destructed.
> Of course, that's easily detected in the base class's destructor.
It causes access violations in the run() function. The destructor of that class still has to notify run() that it needs to end. But just notifying it that it needs to end is not enough. run cannot ever stop as soon as it is notified. The least it might need to do is execute a "return;" statement. But it might do more, so it may continue to access the class members. The destructor of the class, being called in a second thread, does not join() this thread. Instead it drops to the base destructor, after destroying all members of the class. If the thread indeed continues to access members at this point, we have access violations of some kind since the members no longer exist. I'm not sure how this is easily detected in the base class's destructor. By the time it's detected, it's too late, IMO.
To solve this, the destructor of the most inherited class has to call join(). This means you're assuming the user of oo_thread calls join() in the most inherited thread and calls start() in the most inherited thread. You have no way of forcing him to do so or making sure he does so. If he does not, he creates race conditions that could go undetected for some time, depending on their severity.
In the end, you gained little more "OO"-ness from the class-based design. Construction/Destruction of oo_thread end up not being abstractions of creation/destruction of the OS thread routine. They're simply the constructor/destructor of the thread's data. The only thing you gained is a virtual function run(). You gained the ability to define your thread function virtually.
But you could have done this without the entire class wrapper and simply passed the function via boost::bind() to boost::thread() at the appropriate point, using the current interface. A slightly more compact way would be to define a virtual operator() and passing your class instance by reference to boost::thread(), ie, boost::thread(boost::ref(class_object)). The only disadvantage is that now run() or operator() has to be public so bind can access it.
Boost.Threads doesn't force you to use this virtual inheritance model if it's not right for you. You use it if you want, or some other function object technique if that's what you need.
Also, a slightly complex but perhaps minor point, using Boost.Threads semantics, join() is a one-time operation -- it may only be called on a joinable thread, and it produces a non-joinable thread. (In Win32 API semantics, this is not a necessary condition, WaitForSingleObject(hThread, INFINITE) -- the equivalent of join -- does not have as postcondition that the thread is non-joinable). But if we are using the Boost.Threads semantics, this means that you can't create a thread class that contains a thread, and calls join() in the destructor, then inherit from it and call join() again in the destructor. Because then you have two calls to join() in succession, the second one being undefined as the thread is non-joinable at that point. This further eliminates the "OO"-ness of the model that was given. For example, if you have a thread that does "X processing", and can live as a stand alone thread (meaning, it calls boost::thread()/join on its own), you can't inherit from it to provide a thread that does "X
processing and then Y processing", since your inherited thread will also have a call to join() in its destructor causing two joins() in succession, and hence, undefined behavior.
While the Boost.Threads model is different from most other OO thread models I've seen, I concluded it was a more appropriate and correct OO model than the "virtual run" models.
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk