[c++-pthreads] Re: What are the real issues?
Howard Hinnant
hinnant at twcny.rr.com
Wed Jan 7 02:50:09 UTC 2004
I've been debating whether to stay silent and merely suspect I'm an
idiot, or to open my mouth and remove all doubt...
Basic question: Are we trying to solve a problem that really exists?
I'm putting aside for the moment asynchronous cancelation as once you
get into that you give up all hope of releasing resources. For now I'm
simply discussing deferred cancelation.
In C you need an organized system in which to register cleanup handlers
to release resources in case a thread is canceled. You also need a way
to temporarily disable cancelations. And finally you need a mechanism
by which a thread can be notified that someone has requested
cancelation. The POSIX lib neatly supplies all of these requirements.
In C++ we have such a different environment than in C that I am
questioning whether there is a need for a library supported cancelation
mechanism in the first place. One thread can easily request another
thread to cancel itself via a reference to a shared variable (which
need not be global). As long as the "cancelation mechanism" is via a
thrown exception, there is no need for cleanup handlers. And since a
thread can manually check whether its cancel variable has been set or
not, there is no need to disable cancelations (the thread simply
doesn't check whether it has been canceled if it isn't willing to
cancel itself).
In other words, the C++ language makes it so easy to write "cancelable
threads" and "cancel-safe code", are we making an easy problem hard by
trying to code a solution into a library?
For example below is a simple multi-thread helloworld using
Metrowerks::threads (think boost::threads) where the main thread
creates and then cancels a couple of worker threads. Resources are
acquired and released properly, not due to the Metrowerks::threads lib,
but due to the C++ language itself (RAII and basic exception safety).
One might point out that the demo below relies on polling to detect
thread cancelation, and this is true. But pthread cancellation also
seems to rely on polling, so <shrug>.
The demo also avoids global variables to communicate cancelation to the
threads by using function objects instead of functions (a technique
unavailable to the C programmer). And of course, the use of exceptions
and (basic) exception-safe code eliminates the need for cleanup
handlers (also unavailable to the C programmer).
So is the solution we're designing here somehow superior to what the
C++ programmer can easily do for himself already? I'm merely asking,
I'm not claiming to know the answer to that question.
#include <msl_thread>
#include <iostream>
Metrowerks::mutex cout_mutex;
// A is a generic resource holder class
class A
{
public:
explicit A(int id) : id_(id)
{
Metrowerks::mutex::scoped_lock lock(cout_mutex);
std::cout << "A(" << id_ << ") constructed\n";
}
~A()
{
Metrowerks::mutex::scoped_lock lock(cout_mutex);
std::cout << "A(" << id_ << ") destructed\n";
}
private:
A(const A&);
A& operator=(const A&);
int id_;
};
struct cancel {};
// func is a generic (cancelable) thread-worker function object
class func
{
public:
func(int id, volatile bool& b) : id_(id), cancel_(b) {}
void operator()() const;
private:
void job1() const;
void sub_job1() const;
void job2() const;
int id_;
volatile bool& cancel_;
};
void
func::operator()() const
{
unsigned i = 0;
A a(id_);
while (true)
{
try
{
job1();
job2();
Metrowerks::mutex::scoped_lock lock(cout_mutex);
std::cout << i++ << std::endl;
}
catch (std::exception& e)
{
// fix and swallow std::exceptions, but not a cancel
Metrowerks::mutex::scoped_lock lock(cout_mutex);
std::cout << "Fixing " << e.what() << '\n';
}
catch (cancel& e)
{
// Note (but don't swallow) cancel
Metrowerks::mutex::scoped_lock lock(cout_mutex);
std::cout << "Canceling " << id_ << '\n';
throw;
}
catch (...)
{
// Note (but don't swallow) non-std exceptions
Metrowerks::mutex::scoped_lock lock(cout_mutex);
std::cout << "Unknown exception - rethrowing\n";
throw;
}
}
}
void
func::job1() const
{
A a(id_);
sub_job1();
}
void
func::sub_job1() const
{
A a(id_);
if (cancel_)
throw cancel();
}
void
func::job2() const
{
A a(id_);
if (cancel_)
throw cancel();
}
int main()
{
try
{
// main can create and cancel worker threads
volatile bool cancel1 = false;
volatile bool cancel2 = false;
Metrowerks::thread t1(func(1, cancel1));
Metrowerks::thread t2(func(2, cancel2));
Metrowerks::thread::sleep(Metrowerks::elapsed_time(1));
cancel2 = true;
Metrowerks::thread::sleep(Metrowerks::elapsed_time(1));
cancel1 = true;
t2.join();
t1.join();
std::cout << "Done\n";
}
catch (std::exception& e)
{
// Note std::exceptions
Metrowerks::mutex::scoped_lock lock(cout_mutex);
std::cout << e.what() << " - Abnormal termination\n";
}
catch (...)
{
// Note non-std exceptions
Metrowerks::mutex::scoped_lock lock(cout_mutex);
std::cout << "Unknown exception - Abnormal termination\n";
}
}
A(1) constructed
A(1) constructed
A(1) constructed
A(1) destructed
A(1) destructed
A(1) constructed
A(1) destructed
0
...
3093
A(1) constructed
A(1) constructed
A(1) destructed
A(1) destructed
A(1) constructed
A(1) destructed
Canceling 1
A(1) destructed
Done
-Howard
More information about the c++-pthreads
mailing list