|
Boost : |
From: David Abrahams (dave_at_[hidden])
Date: 2002-10-16 13:12:37
I realize this is almost a year too late ;-), but I've been reviewing
the Boost.Threads documentation in detail and I have the following
comments. I also have a whole lot of editorial changes to suggest,
which I'll post separately as a patchset.
* "Thread-safe": this is a term which is well-defined for programs. It
is /not/ well-defined for classes or functions, though the
documentation uses it that way repeatedly. The definition
(definitions.html) says:
"while some thread-safety violations can be determined
statically at compile time, many thread-safety errors can only
be detected at runtime"
Reasoning/justification, please? I refuse to believe that there
exist thread-safety problems which can't be detected via careful
static analysis of the system without proof. Maybe you mean to say
that some kinds of mistakes can be prevented with compile-time
constructs, but some mistakes are logic errors which compilers have
no way to detect?
* Thread State - I think the thread state distinction between "ready"
and "running" is a false one, since the system can move a thread
from "ready" to "running" at an arbitrary time, without provocation,
and the formal description of the library would be a lot simpler if
you just had a "running" state.
Also, you use the term "processor" which is not defined either in
Boost.Threads or in the C++ standard. It is also not detectable by a
user program AFAICT (by time-sharing/context-switching, an OS can
implement as many "virtual processors" as it wants), so I suggest
it's meaningless and should be stricken.
* The definition of "Starvation" gives me pause. Is there any way to
be more-precise about what's meant by "sufficient"?
* The definition of "accessible from multiple threads" is
unintuitive:
"An object [1.8, 1.9] is accessible from multiple threads if
it is of static storage duration (static, extern) [3.7.1], or if
a pointer or reference to it is explicitly or implicitly
dereferenced in multiple threads."
It would be better to change "is explicitly or implicitly" to "can
be". It would be even better to say something like:
"An object [1.8, 1.9] is accessible from multiple threads if
it is stored in the function object passed to a thread's
constructor, it is of static storage duration (static, extern)
[3.7.1], or if a pointer or reference to it is accessible from
multiple threads."
* The second and fourth rows of the table in definitions.html seem
very wrong to me. To review: [read to the end before replying... I
may come to some realizations by the end]
For the same row of the table, the value of an object accessible
at the indicated sequence point in thread A will be determinate
and the same if accessed at or after the indicated sequence point
in thread B, provided the object is not otherwise modified. In the
table, the "sequence point at a call" is the sequence point after
the evaluation of all function arguments [1.9/17], while the
"sequence point after a call" is the sequence point
after the copying of the returned value...
mutex m;
int shared_counter = 0;
Thread A Thread B
+-------------------------------------+------------------------------+
| The sequence point at a call to a | The sequence point after a |
| library function which locks a | call to a library function |
| mutex, directly or by waiting for a | which unlocks the same mutex |
| condition variable. | |
+-------------------------------------+------------------------------+
scoped_lock lkB(m);
++shared_counter;
lkB.unlock(m);
scoped_lock lkA(m);
int x = shared_counter;
Hmm, on closer inspection, this one is OK... though I'd be a lot
more comfortable if the Thread A cell said "after a call" instead
of "at a call". In practice, the only reason you can say "at a
call" is because it's impossible to squeeze anything in before the
locking function gets called and after the evaluation of all
function arguments. And that's prone to confusion, also, since you
can build expressions which call several functions and so contain
several such sequence points
...but thinking about this some more, I'm not sure it has any
meaning for a user. You're telling him that after evaluation of all
arguments, but before the locking function is called, the value of
some shared data will be determinate. But as I said in the
paragraph above, the user can't touch the data there, since there's
no way to insert anything between that touches the shared data at
that point. I don't think your wording makes it OK to do the
assignment in the last line, even though that seems like what you
meant.
+------------------------------+-----------------------------------+
| The sequence point at a call | The sequence point after the call |
| to a library function which | to the library function which was |
| signals or broadcasts a | waiting on that same condition |
| condition variable | variable or signal |
+------------------------------+-----------------------------------+
First of all, the terms "signal" and "broadcast" are not defined
anywhere. Do you mean "calls c.notify_one or c.notify_all" for a
condition c? If so:
condition c;
c.wait(lkB);
c.notify_one() ++shared_counter;
int x = shared_counter; ++shared_counter;
The same problems about the meaningfulness of the claims exist
here, but if I stretch your wording to cover what I think you meant
in row 2, it definitely doesn't make the assignment OK. Worse, I
think it's even a false claim at the undetectable point before
notify_one() is entered, since some thread C can call
c.notify_one(), which allows threads waiting on c to proceed.
Another thing to notice about this row. It's got a kind of
mirror-image relationship to row 2, where it's thread A that's
doing the waiting. Maybe you somehow transposed the columns?
Perhaps I've misunderstood the whole thing, but if so I think it
at least needs some serious rewording for comprehensibility.
Hmm, now maybe the crux of this is:
"the value of an object accessible at the indicated sequence
point in thread A will be determinate and the same if accessed
at or after the indicated sequence point in thread B"
^^^^^^^^^^^
I took this to mean that the sequence point was in thread B, but
the access was in thread A.
Did this actually mean:
"the value of an object accessible in thread A at the indicated
sequence point will be determinate and the same if accessed in
thread B at or after the indicated sequence point"??
If so, I have to re-evaluate the table. The above interpretation
makes row 2 nonsensical, unless we make "unless otherwise modified"
mean that thread A is not allowed to modify the variable after the
mutex is locked. I think I'd have to make the same assumption if I
want row 4 to make any sense. That also goes for row 1. OK, this
must be the right interpretation. Right?
Well, this was so long that I'll have to continue in a followup
message.
-- David Abrahams * Boost Consulting dave_at_[hidden] * http://www.boost-consulting.com
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk