|
Boost : |
From: Eric D Crahen (crahen_at_[hidden])
Date: 2002-08-07 09:23:06
On Aug 7 2002 Yitzhak Sapir wrote:
> Overall, I provided detailed explanation of the problems in thread
> wrapper classes and why they don't give you real advantage in the end.
> You responded with one line unsubstantiated statements:
>
> That is easily detected in the base class. (How do you detect it? By
> the time you detect it, can you do anything useful about the issue to prevent
> any fatal or bad side effects?)
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 {
Condition workRemaining;
Mutex lock;
bool shutdown;
public:
Worker() : shutdown(false) { }
virtual ~Worker() throw() {
}
virtual void run() throw() {
lock.acquire();
while(!shutdown) {
boolean work = false;
while(!(work = somethingToDo()) && !shutdown)
workRemaining.wait(lock);
if(work)
doWork();
}
lock.release();
}
void stop() {
lock.acquire();
shutdown = true;
lock.release();
workRemaining.signal();
}
};
int main() {
Worker w;
w.start();
w.stop();
w.join();
return 0;
}
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.
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.
As an aside, using interruption can make this quite a bit easier to
write(). You could rewrite the run() function like this
// psuedo code
class Worker : public Thread {
// ...
virtual void run() throw() {
try {
Guard<Mutex> g(lock);
while(!workToDo())
workRemaining.wait(lock);
doWork();
} catch(InterruptedException&) {
// quit
}
}
// ...
};
int main() {
Worker w;
w.start();
w.interrupt();
return 0;
}
> 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)
http://cvs.sourceforge.net/cgi-bin/viewcvs.cgi/zthread/zthread/src/
For example, you can use reference counting to achieve this, the details
are in the cvs above - but it makes this syntax possible. I implemented
this in my library,
// psuedo code
class Task : public Runnable {
Condition workRemaining;
Mutex lock;
public:
virtual ~Task() throw() {}
virtual void run() throw() {
try {
Guard<Mutex> g(lock);
while(!workToDo() && !shutdown())
workRemaining.wait(lock);
if(!shutdown())
doWork();
} catch(InterruptedException&) {
// quit
}
}
boolean shutdown() {
// The idea here is that the task knows when its time to quit
// since you just want to detach the thread and leave it.
}
};
int main() {
Thread t;
t.setDaemon(true);
t.run(new Task());
return 0;
}
The thread object can be safely destroyed because you've placed all the
data the task is dependant on somewhere else (in the Task object).
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.
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.
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.
- Eric
http://www.cse.buffalo.edu/~crahen
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk