Peter Dimov wrote:
Yuval Ronen wrote:
[...]
If we agree that threads cannot be copied,
We don't. More details below.
Sorry for jumping in between. But I always was wondering what "copying a
thread"
could mean. Innocently I would expect creating a second instance of the
thread similar
to a "fork" of processes in unix. But I don't think this is what you
have in mind.
Otherwise copying the whole state of a thread is not of much sense
either (thread
needs to be scheduled.) I think noncopyable is a property of a thread,
and the
design simply refelcts this fact, but I might be wrong in this respect
and would like
to hear what I am missing.
it means that each thread can be
represented by only one thread object. Pass the address of thread
objects and compare them, if you really want to. It'll give you the
exact same result.
The problem with your reasoning is that the thread object may have
been destroyed. Its lifetime is not tied to the lifetime of the thread.
This is true. But where is the problem? Once I have deleted the thread
object (not the thread!) I am not
able to compare it to anything, since the memory is invalid, isn't it?
As I see it, the demand for thread ID's or references mainly come from
the attempt to
communicate with the thread. (Are there really other usages?) So not
having a thread ID
is no drawback at all, since this problem can be solved easily by other
means.
I append a small example showing how a thread can communicate with another,
using a control object with automatic lifetime:
Some remarks
1) from inside the thread I use a tls that holds the communication object
2) from outside I use a wrapper object
3) control of lifetime of this object is tricky, since it must last as
long as both
communicating threads have agreed to dipose it.
#include
#include
struct control_impl;
boost::thread_specific_ptr g_control;
struct control_impl {
control_impl() : stopped(false), ref(1) {}
void stop() {
boost::mutex::scoped_lock lk(monitor);
stopped = true;
control_changed.notify_one();
}
void wait_until_stopped() {
boost::mutex::scoped_lock lk(monitor);
while(!stopped)
control_changed.wait(lk);
}
void release() {
boost::mutex::scoped_lock lk(monitor);
if (--ref == 0) {
lk.unlock();
delete this;
g_control.reset(0);
}
}
void addref() {
boost::mutex::scoped_lock lk(monitor);
++ref;
}
boost::mutex monitor;
boost::condition control_changed;
bool stopped;
int ref;
};
// the control struct to be used from outside the thread
struct control {
control() { pimpl = new control_impl; }
~control() { pimpl->release(); }
void stop() {pimpl->stop(); }
control_impl* pimpl;
};
// the functions to be used from inside the thread
void wait_until_stopped()
{
(*g_control)->wait_until_stopped();
}
void register_control(control* p)
{
p->pimpl->addref();
g_control.reset(new control_impl*(p->pimpl));
}
void release_control()
{
(*g_control)->release();
}
// the user program
void foo() {
wait_until_stopped();
}
void run(control* p) {
register_control(p);
foo();
release_control();
}
int main(int argc, char* argv[])
{
control* pc = new control;
boost::thread* pt = new boost::thread(boost::bind(run,pc));
pc->stop();
delete pc;
pt->join();
return 0;
}
In a real implementation of course the user code should not have access to
the control_impl, so it cannot mess with the reference counting.
I admit however that the real question remains: What is a thread object
then at all?
At the momnet its only purpose is to serve as an access point for join.
I think it s worth about thinking about a generalized mechansim that will
allow access of user defined structures from inside and outside a thread.
Roland