What are the real issues?
Matt Austern
austern at apple.com
Tue Jan 6 19:33:47 UTC 2004
I'm just trying to trace back why people see C++ thread cancellation as
a problem.
First: the usual assumption has been that thread cancellation is most
naturally represented in C++ as an exception. That's because the POSIX
model for thread cancellation is that if you cancel a particular
thread, then, the next time the thread reaches one of a set of
well-defined cancellation points, it'll perform some cleanup and then
stop. In C the cleanup is via handlers that you register. We could
have that model in C++ as well, but most C++ programmers would think
it's very strange that you would stop execution at some point without
cleaning up the stack. And once you're talking about stack cleanup,
i.e. invoking destructors, you're talking about something that's close
enough to an exception that it makes no difference.
The Itanium C++ ABI, which gcc adopted, made cancellation a special
kind of exception, "forced unwinding", so that a thread can't just
catch the cancellation exception and swallow it. The idea was that a
thread may disable cancellation tempoarily, but that once a
cancellation has begun it can't be thrown away. This was a slightly
controversial decision at the time, and maybe it's at the heart of the
real issue.
Second: the immediate issue was that some people saw a contradiction
between this model and the C++ library specification. For example,
POSIX says that read() is a cancellation point. But it's reasonable to
assume that std::istream:;read() invokes POSIX's read() system call,
and the C++ standard says that (under ordinary circumstances)
std::istream doesn't throw. If the cancellation exception were an
ordinary exception instead of special forced unwinding, what would
happen would be that istream would invoke its streambuf, the streambuf
would call read(), read would throw a cancellation exception, then
istream would catch the exception, swallow it, and set and error flag.
Forced unwinding means that the cancellation exception will propagate
even though istream tries to swallow it. This means that an exception
will propagate from istream when the C++ standard says it's not
supposed to.
Third, some people simply object to having cancellation be an exception
(or anything that looks like an exception): it means there are some
functions that can throw exceptions in the presence of threads that
wouldn't throw in a single-threaded program.
It's actually pretty hard for me to imagine any model for thread
cancellation that's very different from the POSIX. Anything that
doesn't involve thread execution stopping sounds more like a
communication mechanism than a cancellation mechanism. Seems to me
that the only real issues for debate are which functions are
cancellation points, how a thread can enable and disable cancellation,
whether a thread should be allowed to disregard a cancellation request
once it has been received, and what kind of cleanup a thread performs
before it stops.
My feeling: it's just plain inevitable that a multithreaded program has
more functions that might throw than a single-thread program. Dealing
with this is part of what it means to make a program thread-safe. We
might argue that POSIX defines too many cancellation points, and that
cutting it down to a smaller number (and, especially, getting rid of
the functions that POSIX says may or may not be cancellation points)
would make it easier to write correct code.
But as I said, I think the really fundamental issue is whether a thread
should be allowed to receive a cancellation request, start to do some
work as a result of the request, and then decide that it doesn't want
to be cancelled. If we think that's reasonable then I think what we
should probably do is abandon the notion of forced unwinding and make
cancellation into an ordinary exception. If we do that then the
std::istream contradiction goes away. The cost, of course, is that it
might become surprisingly hard to cancel some threads.
--Matt
More information about the c++-pthreads
mailing list