|
Boost : |
From: williamkempf_at_[hidden]
Date: 2001-11-12 12:19:31
--- In boost_at_y..., "Kevin S. Van Horn" <kevin.vanhorn_at_n...> wrote:
> I finally managed to eke out enough free time to finish reviewing
and
> commenting on the documentation for the Boost threads library (as a
prelude to
> attempting some formal correctness proofs.) Here are my remarks.
They
> include a mixture of comments on design, exposition, wording, and
sometimes
> grammar.
>
> GENERAL COMMENTS
> ================
>
> Fairness and strongness
> -----------------------
>
> Missing from the documentation is a discussion of the three levels
of
> increasingly-strong guarantees for mutexes, which parallel the same
categories
> for semaphores:
>
> 1. Weak mutexes. When a thread unlocks a weak mutex and there are
other
> threads blocked waiting on the mutex, the mutex may enter the
unlocked state
> for a short period of time until some thread locks it again. The
locking
> thread may not have been one of the threads waiting at the moment
the mutex
> was unlocked.
>
> Consider the following scenario with a weak mutex. Thread A is in
a loop
> where it repeatedly acquires the mutex, does something, then
releases the
> mutex:
>
> while (1) {
> <processing outside of the critical section>;
> boost::mutex::scoped_lock l(mtx);
> <critical section>;
> }
>
> If there are one or more other threads waiting to acquire the
mutex, thread A
> may monopolize the mutex, starving the other threads so that they
*never*
> acquire the mutex. This can occur because thread A may reacquire
the mutex
> after releasing it, before one of the waiting threads manages to
acquire it.
>
> Weak mutexes are nearly useless. As far as I can tell, it is
impossible to
> build starvation-free synchronization mechanisms on top of them.
As one
> example, consider trying to build semaphores on top of weak
mutexes. The
> acess to the integer giving the semaphore count must be protected
by a mutex.
> If this is a weak mutex, then a signal/up_count/V operation may
block
> forever if there is heavy activity on the semaphore!
>
> *** I have looked over the POSIX implementations of condition
variables and
> the various Mutex types, and several of these are weak mutexes. ***
From a documentation stand point I can't really address this with out
causing (theoretical at least) problems with portability. However,
the implementation is a definate issue. If you have solutions for
the implementation I'd be very interested.
> 2. Strong mutexes. Suppose that a thread A holds a lock on the
mutex while
> there are other threads blocked trying to acquire the mutex. When
A "unlocks"
> the mutex, it does not actually enter an unlocked state; instead,
the mutex
> remains locked, but one of the blocked threads becomes unblocked
and acquires
> the mutex. This prevents any one thread from monopolizing the
mutex.
> However, it is not sufficient to prevent starvation: two threads in
a
> lock/unlock loop such as presented in the discussion of weak
mutexes can
> starve a third by playing a game of "keep away". That is, when
thread A
> releases the mutex, thread B always acquires it, and when thread B
releases
> the mutex, thread A always acquires it, leaving thread C forever
waiting to
> acquire the mutex.
Again, I can't really mention this in the documentation. However,
starvation can occur even with "fair" mutexes (by one definition of
fair). POSIX mutexes, for instance, are open to priority starvation
problems if you aren't careful.
> 3. Fair mutexes. A fair mutex is a strong mutex with one additional
> guarantee: as long as every thread that acquires the mutex
eventually releases
> it, any thread that is blocked on the mutex will eventually require
it. Note
> that mutexes that use FIFO scheduling are fair, but a mutex can be
fair
> without using FIFO scheduling.
>
> I seem to recall that one can implement fair semaphores on top of
strong
> semaphores, though I do not recall the algorithm. I'm not sure
whether or not
> one can implement a fair mutex on top of strong mutexes. Note the
important
> difference between binary semaphores and mutexes: any thread can
unlock a
> locked binary semaphore, but only the locking thread can unlock a
locked
> mutex. This added capability of binary semaphores is very useful
for ensuring
> lack of starvation in synchronization algorithms.
>
> As with mutexes, condition objects can also be strong or fair. (The
> definition of the notify_one() and notify_all() operations rules
out weak
> condition objects.) Suppose that a thread A is blocked in a wait()
on a
> condition object. If the condition object is fair, then A can only
remain
> blocked forever because of a lack of notify() calls on the
condition object,
> and not because A always gets passed over in favor of some other
thread to
> unblock on a notify_one() call. The Boost.Threads documentation
needs to
> specify whether or not condition objects are fair in this sense.
Again, I don't think the documentation can do this with out causing
problems with portability.
> Also, the
> documentation should make clear that there are two kinds of
blocking in a
> wait() operation: a thread may be blocked on the condition object
itself, or
> it may have been unblocked and then subsequently blocked trying to
reacquire
> the mutex.
I'm not sure how this knowledge (which should be obvious when
thinking about it) will be useful.
> If at all possible, and if the implementation cost is not too high,
all
> mutexes and condition objects should be fair. Otherwise you run
the risk of
> users having mysterious, intermittent and irreproducible
thread "lock-ups."
> If making the mutexes or condition variables fair adds a significant
> implementation cost, then Boost.Threads should have both efficient-
but-unfair
> and fair-but-less-efficient mutexes.
In general, your comments are very relevant here. I can't document
some of this because I need the leeway for implementing over existing
MT libraries, but these are issues that should be addressed
sufficiently for a standardized library (where the implementers won't
be restricted in the same manner for the implementation). So I'll
need to document this stuff in some way. I'm also very concerned
about your findings on the implementation. If you have any thoughts
on how to correct these areas I'd be very interested.
> Handling of programmer errors
> -----------------------------
>
> There are many places where the documentation specifies that
exceptions *will*
> be thrown if some precondition is not met. Do you really want to
guarantee
> this, or do you just want to say that the exception *may* be
thrown? At debug
> time you do not want exceptions thrown for what amounts to
programmer error
> (e.g., unlocking a mutex that you do not have locked), as this
throws away a
> lot of useful information as the stack unwinds. At debug time it
is much
> preferable for the program to dump core so that you can examine the
core dump
> with a debugger and have full information on the context of the
error. I
> recognize that a core dump is unacceptable for some production
environments,
> so it would be nice to be able to choose between the core-dump and
exception
> options via a compile-time flag.
Complex decision and this has been discussed a lot and honestly the
final decision probably isn't reached yet. Generally, the
documentation is correct IMHO, though the implementation may want to
assert on debug builds.
> In addition, I would suggest that even when you do throw an
exception, the
> exception object should include the file and line number. (See
discussion
> below for overview.html.)
A useful concept for debuggin purposes, but not one that should be
gauranteed I think. In other words I'll consider adding this to the
exception's what() but I think it would be wrong to put this in the
documentation as a requirement (thinking in terms of standardization).
> Atomic reads and writes
> -----------------------
>
> I think a discussion is needed of why even reads and writes of
variables of
> primitive types may not be atomic, and that there is *one* type for
which
> reads and simple writes are guaranteed atomic (sig_atomic_t).
Outside of the scope of the documentation, though discussion of this
topic here and possibly in the FAQ would make sense.
> DOCUMENT-SPECIFIC COMMENTS
> ==========================
>
> overview.html:
> --------------
>
> Quote: "Priority failures (priority inversion, infinite overtaking,
> starvation, etc.)"
>
> Comment: The programmer cannot do anything about priority
inversion; that has
> to be addressed at the OS level.
This isn't true. The simplest way for the programmer to avoid
priority inversion is to never share resources between threads of
varying priority. This can be done by changing a thread's priority
before and after acquiring a mutex, for instance, and in fact POSIX
specifies a mechanism in which this can be automatically done. This
is an area in which Boost.Threads may need some modification, but
even with out this support you can avoid this problem.
> I don't believe that "infinite overtaking"
> and "starvation" are defined anywhere.
Probably not. We'll need to add definitions.
> I'm not sure that "priority failure"
> is an appropriate term to describe starvation. Absence of
starvation just
> means that a thread waiting its turn to do something does,
eventually, get its
> turn; the concept is meaningful even in the absence of any notion
of thread
> priorities.
I agree and disagree. Starvation is often caused by priority
failures. You're quite correct that it can also be caused in other
ways so the documentation should be made clearer here, but it's not
strictly incorrect.
> Quote: "Every multithreaded program must be designed carefully to
avoid race
> conditions and deadlock."
>
> Comment: I would add starvation to the list of things to avoid.
This is a
> commonly-overlooked error in multithreaded code.
Agreed.
> Quote: "Common requirements for all Boost.Threads components."
>
> For implementors they are requirements, but for users (the great
majority of
> those who will be reading the document) they are guarantees /
specifications.
Hmmm... different perspective. I'll look at this.
> introduction.html
> -----------------
>
> Quote: Under "Flexiblity" it says, "When functionality might be
compromised by
> the desire to keep the interface safe, Boost.Threads has been
designed to
> provide the functionality, but to make it's use prohibitive for
general use."
>
> Comment: I have no idea what that final clause means. Also, "it's"
means "it
> is"; the possessive of "it" is "its." (You write "whose",
not "who's"; "his",
> not "he's"; "theirs", not "their's; and so forth.)
Yes, that would be a typo ;). I'm not sure how to reword the clause
to be more meaningful, however.
> Quote: "Recently there has been a lot of research in other
concepts, such as
> in 'Communicating Sequential Processes.'"
>
> Comment: CSP is not new. It wasn't new in in 1985, when I first
learned about
> it.
Depends on your definition of new ;). However, I was referring to
new research, not to the invention of the concept. There is a lot of
new research for CSP that's been started recently.
> faq.html
> --------
>
> Quote: "1. Are lock objects thread-safe?"
>
> Comment: The hyphen is necessary only when the adjective
phrase "thread safe"
> immediately precedes a noun or noun phrase. For example, "f() is a
> thread-safe function." The hyphen indicates grouping: "thread"
modifies
> "safe", and not "safe function".
OK.
> Quote: "They are meant to be short lived objects..."
>
> Comment: Should be "short-lived" objects: "short" modifies "lived",
not "lived
> objects."
OK.
> Quote: "One easy way of doing this [locking mutexes in a standard
order] is to
> use each mutex's address to determine the order..."
>
> Comment: This is not portable. The standard only allows
comparisons other
> than == and != between pointers that point into or just past the
end of the
> same object. The example code for item 5, using "<" and ">" to
compare
> mutex pointers, is not portable for this reason. Interestingly
enough,
> however, I seem to recall that function objects of type std::less<T
*> are
> guaranteed to provide a complete ordering.
That's a debate I don't want to get into, because I lose every time.
If it's enough to satisfy everyone I can simply change the code to
use std::less.
> Quote: The example code for item 5 includes the lines "m_value =
> other.m_value;" and "return m_value;", which are protected by mutex
locks.
>
> Comment: It's worth mentioning why the protection is necessary:
with the
> exception of the sig_atomic_t type, you don't know know whether
reading or
> writing a value of even a primitive type can be done as a single
atomic action
> (single memory read/write.)
There is no standard sig_atomic_t type, so this comment will just
lead to confusion. As it is, I think the reasons are self obvious.
> Quote: [Under item 6] "The internal state of mutex types could have
been made
> mutable, with all lock calls made via const functions, but this
does a poor
> job of documenting the actual semantics."
>
> Comment: In fact, such a const declaration would be outright
incorrect, since
> the logical state of a locked mutex clearly differs from the
logical state of
> an unlocked mutex!
Depends on your definition of incorrect, which is precisely why
things are worded the way they are.
> Quote: "Semaphore was removed as too error prone. The same effect
can be
> achieved with greater safety by the combination of a mutex and a
condition
> variable."
>
> Comment: This is not so easy to do correctly if you wish to avoid
> starvation!
Care to elaborate?
> definitions.html
> ----------------
>
> Quote: "Thread State." [Followed by definitions of "Ready"
and "Running".
>
> Comment: You are getting into scheduler implementation details that
have no
> place in a behavioral specification. The usual way of reasoning
about
> concurrent programs is that you assume nothing about the relative
speeds of
> different threads, and that arbitrarily long (but finite) delays
may occur at
> any point in the execution of a thread. The change of state from
Running to
> Ready and then eventually back to Running again looks just like an
arbitrary
> pause in the execution of the thread.
I don't see any scheduler implementation details here. There is
certainly nothing here that assumes anything about the relative
speeds of different threads or about delays.
> Quote: [Under "Race Condition"] "The result of a race condition may
be a bit
> pattern which isn't even a valid value for the data type."
>
> Comment: It's worth mentioning why this is so (see previous comment
on
> atomicity.)
I think you're trying to overspecify the terms of atomicity for
native types. There's no knowing if a native type can be manipulated
atomically in a portable manner and there's no standard sig_atomic_t
type. The description given here is accurate and complete with out a
discussion of non-portable concerns about atomic read/write
operations.
> Quote: "A priority failure (such as priority inversion or infinite
> overtaking)..."
>
> Comment: Need to define priority inversion and infinite
overtaking. Also need
> to define and discuss starvation.
I'll look into this.
> rationale.html
> --------------
>
> Quote: "However, computer science research has shown that use of
these
> primitives is difficult since there's no way to mathematically
prove that a
> usage pattern is correct, meaning it doesn't result in race
conditions or
> deadlocks.
>
> Comment: The above statement is false. CS researchers have been
> proving the correctness of concurrent algorithms using semaphores,
mutexes, or
> even just reads and writes of shared variables with busy waiting
(e.g., the
> Bakery Algorithm) for decades. It would be correct, however, to
say that
> doing so is *difficult* for low-level synchronization constructs.
OK, some re-wording is needed here. What was meant is that there's
no universal formula for proving this. Each algorithm requires
different proofs, which, as you say, are non-trivial and difficult to
do.
> Quote: "Though it's possible to create a lock object outside of
block scope
> and to share it between threads to do so would not be a typical
usage."
>
> Comment: I would make a stronger statement: "to do so would very
likely be an
> error." Also, you need a comma after "between threads."
OK.
> Quote: [Under "Rationale for Non-Copyable Thread Type".] "...
could provide
> more efficient reference management then wrappers."
>
> Comment: "then" (order) should be "than" (comparison.)
Typo. I'll fix.
> Quote: "Analysis of whether or not these arguments would hold true
don't
> appear to bear them out."
>
> Comment: "Analysis" is the simple subject, so "don't" should
be "doesn't."
OK.
> Quote: "...but thread_ref no longer suffers because of it's
reference
> management,..."
>
> Comment: "it's" should be "its."
Yep. My favorite typo (well, one of).
> mutex_concepts.html:
> --------------------
>
> There is a systematic misuse of "concept" and "model" in this
document. To
> review:
> - A concept is a set of requirements on a type.
> - Mutex is a concept.
> - A type T is a model of concept C if T satisfies all of the
requirements of
> C.
> - The type boost::mutex is a model of Mutex.
> - Actual objects are not models of concepts nor of classes.
>
> The phrase "mutex model" is repeatedly used when "mutex" or "mutex
object"
> (object whose type is a model of Mutex) is the correct term.
Sometimes "mutex
> concept" is used when "mutex object" would be correct.
Yeah, several people have been hammering this into me for some time.
I'll look at the documentation (again).
> Quote: "A mutex (short for mutual-exclusion) concept serializes
access..."
>
> Comment: Concepts don't *do* anything; they're just collections of
type
> requirements. You mean "mutex" or "mutex object".
>
> Quote: "A model that implements Mutex and its refinements has two
states:"
>
> Comment: Concept models are types, and types don't have state;
objects do.
> Suggested rewrite: "A mutex (object whose type models Mutex) has two
> states..." The "and its refinements" is redundant, since any type
that
> models a refinement of Mutex also models Mutex itself.
>
> Quote: "mutex model object" [Appears in many places.]
>
> Comment: Replace with "mutex" or "mutex object."
>
> Quote: "mutex model" [Appears in many places; e.g., "unlock a mutex
model."]
>
> Comment: Replace with "mutex" or "mutex object."
>
> Quote: "With this pattern, a lock concept is employed where the
lock model's
> constructor locks the associated mutex..."
>
> Comment: "lock concept" and "lock model" should be "lock object."
>
> Quote: "... mutex model ..."
>
> Comment: Should be "model of Mutex" or "Mutex model", as "Mutex,"
not "mutex,"
> is the name of the concept.
>
> Quote: "With an unspecified locking strategy, when a thread
attempts to
> acquire a lock on a mutex model for which the thread already owns a
lock the
> operation results in undefined behavior. When a mutex model has an
> unspecified locking strategy the programmer must assume that the
mutex model
> instead uses an unchecked strategy."
>
> Comment: The model *may* use an unchecked strategy. If the
strategy is
> unspecified, the programmer shouldn't assume that that the thread is
> *guaranteed* to deadlock; "undefined behavior" means that anything
can happen,
> including deadlock, aborting the program, or the computer
going "Star Trek"
> and exploding in a shower of sparks :-).
>
> Scheduling policies: What is the difference between "Undefined" and
> "Unspecified"?
Not enough to be relevant, I suppose. I'll remove Undefined.
> Under "Priority Driven", it might be worth mentioning the
> usual way of handling priorities without starving low-priority
threads: use
> aging, where a thread's priority increases the longer it waits,
then goes back
> to its original level after it is serviced.
I'll look into this.
> Quote: [Under "Scheduling Policies," "Undefined"] "Users should
assume that
> low-priority threads may wait indefinitely, and that threads of the
same
> priority acquire the lock in essentially random order."
>
> Comment: Does "indefinitely" mean "forever", or just "an
unspecified but
> finite amount of time"? See also general notes on weak vs. strong
vs. fair
> mutexes.
It means what it says ;). There is no "forever" since the program
will execute in a finite amount of time, but the distinction isn't
worth arguing about. You can't write reasonable code regardless of
whether it's "forever" or "an unspecified but finite amount of time".
> Quote: [In Mutex concept, for expression "M::scoped_lock".] "A
type meeting
> the ScopedLock requirements.
>
> Comment: Suggest "A model of ScopedLock." Similar comment applies
to TryMutex
> and TimedMutex.
OK.
> Quote: [In TryMutex concept.] "A TryMutex must meet the Mutex
requirements."
>
> Comment: Suggest "TryMutex is a refinement of Mutex." Similar
comment applies
> to TimedMutex.
OK.
> Quote: "Boost.Threads currently supplies six classes which model
mutex
> concepts."
>
> Comment: Suggest "Boost.Threads currently supplies six models of
Mutex." In
> the table underneath this, suggest replacing "Classes Modeling the
Concept"
> with "Models."
OK.
> mutex.html
> ----------
>
> Quote: "The mutex, try_mutex and timed_mutex classes are full
featured models
> of the Mutex, TryMutex, and TimedMutex concepts."
>
> Comment: Somewhat redundant. Suggest "The mutex, try_mutex, and
timed_mutex
> classes are models of Mutex, TryMutex, and TimedMutex respectively."
OK.
> Table in Introduction: There is a column labeled "Implementation
defined Lock
> Type." This should be removed as an implementation detail on which
library
> users should not rely. Take a look at the description of the
standard library
> containers; do they tell you the "implementation defined iterator
type" for
> each container class?
Well, at one time there was a reason for documenting these
implementation details, but since we've fully moved this into detail
land you're right. I'll fix this.
> Quote: "The mutex, try_mutex and timed_mutex classes use an
Unspecified
> locking strategy."
>
> Comment: This isn't really a strategy; it's a refusal to specify the
> strategy. Suggest "The mutex, try_mutex and timed_mutex locking
strategies
> are unspecified."
Well, Unspecified *was* listed as a viable strategy, but this is a
better description any way. I'll change it.
> Quote: "Programmers should assume that threads waiting for a lock
on objects
> of these types acquire the lock in a random order, even though the
specific
> behavior for a given platform may be different."
>
> Comment: "Random" has probabilistic connotations.
Suggest "Programmers should
> make no assumption as to the order in which waiting threads acquire
a lock."
> One exception: for strong mutexes, programmers can assume that when
a thread
> holding a mutex releases it and there are threads blocked on the
mutex, one of
> the waiting threads acquires the lock immediately. (At least,
logically this
> is what happens.)
Again, I don't think I can (or should) mention strong mutexes here.
Otherwise I agree and I'll make the changes.
> Quote: "Class mutex Synopsis"
>
> Comment: It's worth mentioning that mutex is a model of Mutex and
NonCopyable,
> and that the class provides no additional facilities beyond the
requirements
> of those two concepts. Similar comment applies to try_mutex and
timed_mutex.
I'll look at this.
> recursive_mutex.html
> --------------------
>
> The comments for mutex.html apply here also.
>
> Quote: "Attempts to unlock them by a thread that does not own a
lock on them
> will result in a lock_error exception being thrown."
>
> Comment: Why not "undefined behavior"? Do you really mean to
*guarantee* that
> a lock_error exception will be thrown? During debugging, I would
prefer an
> assert() that causes a core dump over throwing an exception, as the
latter
> loses all information as to the context in which this programming
error
> occurred.
Complex subject. I'll continue to look at this.
> lock_concept.html
> -----------------
>
> As with mutex_concepts.html, this document systematically
misuses "concept"
> and "model." The phrase "lock model" should, in most cases, be
replaced by
> "lock object."
>
> Quote: "The lock concepts provide exception safe means for locking
and
> unlocking a mutex model."
>
> Comment: Concepts are specifications, not mechanisms.
Suggest "Lock objects
> provide ... a mutex."
>
> Quote: "Instances of lock models are meant to be short lived."
>
> Comment: Suggest "Lock objects are meant to be short lived."
>
> Quote: "Lock models must maintain state to indicate whether or not
they've
> been locked and this state is not protected by any synchronization
concepts."
>
> Comment: Concepts are not mechanism, and thus cannot protect.
Suggest "Lock
> objects maintain state to indicate whether they've been locked, and
this state
> is not protected by any synchronization mechanism."
>
> Quote: [Lock requirements table.]
> Expression
> (&clk)->operator const void *()
>
> Comment: I would suggest
> Expression Effects
> clk Convertible to void *. Returns nonzero if ...
Hmm... I'll look at this. Makes sense, just want to make sure I
follow what the standard already does.
> Quote: [Lock requirements table.]
> Expression Effects
> clk.locked() Returns a bool, (&clk)->operator const void *() != 0
>
> Comment: I would suggest
> Expression Effects
> clk.locked() clk != (void *)0
>
> Quote: [Lock requirements table.]
> Expression Effects
> lk.lock() Throws a lock error if locked(). If the associated
mutex
> is already locked by some other thread, places the
current
> thread in the Blocked state until the associated
mutex is
> unlocked, after which the current thread is placed
in the
> Ready state, eventually to be returned to the
running state.
>
> Comment: Do you mean to guarantee that an exception will be
thrown? When
> debugging, I would rather have an assert() that causes a core dump,
so that I
> can investigate the context in which the programming error occurred.
> Secondly, the wording seems to imply that when the thread holding
the lock on
> a mutex releases it, *any and all* threads blocked on the mutex get
> unblocked. Finally, the discussion of a Ready state brings in
thread
> scheduler implementation details that should be of no concern to the
> programmer.
>
> Quote: [Lock requirements table, entry for "lk.unlock()."] "If !
locked(),
> throws lock_error..."
>
> Comment: Do you really mean to *guarantee* that an exception is
thrown?
>
> Quote: "A ScopedLock must meet the Lock requirements."
>
> Comment: Suggest "ScopedLock is a refinement of Lock."
>
> ScopedLock Concept: If mutex m is of type M, isn't it required that
type L and
> M::scoped_lock be the same type in the expression "L lk(m);"?
>
> ScopedTryLock Concept: Similar comments as for ScopedLock. Also,
for
> lk.try_lock() the table guarantees that an exception will be thrown
for a
> programming error.
>
> ScopedTimedLock Concept: Same comments as for ScopedTryLock and
ScopedLock.
> Other comments:
> - I'm uncomfortable with the use of absolute times (xtime) instead
of
> durations. The xtime document refers to "the duration since the
epoch
> specified by clock_type." This "clock_type" is passed to
xtime_get(), but
> how does the timed lock know which clock_type value to use?
Absolute times are basically required for condition variables
(because of the looping) and so I've used absolute times for
consistency. This was the general consensus I got (from the
admitedly limited responses) when I discussed this during development
on this list. As for the "clock_type"... there's currently only one
valid clock_type making this a non-issue. Things will change when we
settle on a true Boost solution for times/durations.
> - lk.timed_lock(t) is specified to "return true if sucessful within
the
> specified time t, otherwise false." You can't really guarantee
this, due to
> scheduler delays, or delays in acquiring the auxiliary mutex used
in
> the POSIX implementation of timed_mutex. The best you can say is
that
> the thread won't wait forever, but will wait at least until time
t before
> giving up.
Neither description is fully accurate, but your is closer. I'll work
on this.
> Models table at end of document: The "Refines" column is wrong for
every
> entry. Every entry should have "Lock" in its "Refines" column.
Quite true.
> scoped_lock.html:
> -----------------
>
> I'm not sure this document is necessary or desirable. The C++
Standard
> doesn't go into separate detail on C::iterator for each container
class C; it
> just says which concept it models, and any additional needed info
is included
> in the description of class C. Likewise, all of the information in
this
> document that users should see belongs either in the description of
the
> appropriate concept or in the description of the mutex class itself.
Another consequence of having the scoped_lock classes in a nebulas
detail/non-detail state during development. I'll fix this.
> The mutex_type typedef is not mentioned in the Lock concept; it
should be.
Hmmm... probably true. It was put there for use by boost::condition,
but this usage is beneficial for other generic usages. I just didn't
think this one through.
> Quote: "Although this class is an implementation detail..."
>
> Comment: Stop right there. Implementation details don't belong in
> specifications, except as examples clearly labeled as possibilities
only.
The idea was that developers could use the templates for the Lock
concepts in their own Mutex refinements. However, the types have now
been fully moved into detail land and should simply not be documented.
> Quote: [For "operator const void * () const"] "If the associated
mutex is
> currently locked, a value convertible to true, else a value
convertible to
> false."
>
> Comment: Don't you mean "If the associated mutex is currently
locked BY THIS
> OBJECT..." Also, void * values are NOT convertible to bool,
although they can
> be used as the test of an if, while, or for, and can be used as an
argument to
> &&, ||, and !.
Correct on both counts.
> Quote: "A const void * conversion is considered safer than a
conversion to
> bool."
>
> Comment: You should explain why.
This is the rationale, and re-iterating this rationale every time
it's used is needless IMHO. However, it's a non-issue when I remove
this documentation ;).
> scoped_timed_lock.html
> scoped_try_lock.html
> ----------------------
>
> See comments for scoped_lock.html.
>
>
> condition.html
> --------------
>
> Quote: "...a mutex object modeling a Mutex concept."
>
> Comment: Types model concepts; objects do not.
>
> Quote: "...a lock object modeling a Lock Concept..."
>
> Comment: Types model concepts; objects do not.
>
> Quote: "The mutex must be locked prior to waiting on the condition,
> which is ensured by passing a lock object modeling a Lock Concept to
> the object's wait functions."
>
> Comment: This doesn't ensure that the mutex is locked, although it
does ensure
> that the mutex gets unlocked.
Actually, it does ensure it, through a call to locked() on the lock
object passed in.
> Quote: "While the thread is waiting on the condition object, the
mutex
> associated with the lock is unlocked."
>
> Comment: The use of "While..." would seem to indicate that "is
unlocked" is
> not the passive form of "lock", but a simple statement of the state
of the
> mutex. That is, the sentence seems to say that the mutex remains
unlocked
> during the entire time that the thread is waiting on the condition
object.
> Suggest "Upon blocking on the condition object, the thread
unlocks/releases
> the mutex."
>
> Quote: "When the thread returns from a call to one of the condition
object's
> wait functions, the mutex is again locked."
>
> Comment: Does not make clear who locks the mutex, and is not clear
on whether
> this happens just before or just after the call. Suggest "The wait
functions
> reacquire the mutex just before returning."
I'll work on this.
> Quote: "The tricky lock/unlock/lock sequence is performed
automatically by the
> condition object's wait function."
>
> Comment: Only the unlock/lock part is performed by the wait
function.
True. This description needs some cleaning up.
> Quote: "notify_one()... Effects: If there is a thread waiting on
*this, change
> that thread's state to ready."
>
> Comment: This is not completely accurate. After being unblocked,
the thread
> then has to reacquire the mutex, which may cause it to block
again. Also, the
> issue of fairness (lack of starvation) needs to be addressed: in
the case
> where some thread t is waiting on a condition and there are always
multiple
> threads waiting when a notify_one() occurs, is there any guarantee
that
> eventually thread t will be chosen, or may thread t *always* be
passed up in
> favor of another thread and wait forever on the condition? (I'm not
> considering here the issue of subsequently reacquiring the mutex.)
The re-locking semantics are covered by the documentation of the wait
methods. I'm not sure that changing the wording here would be
useful. And again, I'm not sure that mentioning the fairness is
possible here, and even with fair implementations you can get the
same problems, as illustrated by starve_phil.
> Quote: "void wait(ScopedLock & lock); ... All effects occur in an
atomic
> fashion."
>
> Comment: This is not and cannot be true. The initial
unlock/suspend can be
> done atomically, but becoming unblocked is a two-stage process: (1)
the thread
> gets awakened by some other thread calling notify_one() or
notify_all(); (2)
> the thread reacquires the mutex. For conditions, we really need to
> distinguish two separate blocking states: blocked on the condition
itself, and
> blocked on the mutex while attempting to reacquire the mutex.
Technically accurate, but I'm not sure the distinction is useful.
> Quote: "timed_wait ... Throws: lock_error if !lock.locked()"
>
> Comment: Do you mean to guarantee that an excpetion is thrown?
This is what ensures the mutex is locked ;). Seriously, though, I'm
still thinking about this issue.
> Quote: "bool timed_wait(ScopedLock & lock, const xtime & xt);"
>
> Comment: See my previous reservations about using an absolute time
instead of
> a duration.
The rationale for absolute times is well documented by POSIX. Usage
of durations for timed waits on conditions is very problematic given
the presence of "spurious wakeups" and the need for checking the
predicate in a loop. Durations require re-calculations through each
loop iteration which complicates the code and can effect the over all
performance.
> Quote: "timed_wait ... Returns: false if xt is reached, otherwise
true."
>
> Comment: You can't really guarantee this. See my previous comments
on
> ScopedTimedLock.
>
>
> thread.html:
> ------------
>
> Quote: "This includes completion of all thread cleanup handlers,..."
>
> Comment: What thread cleanup handlers? Boost.Threads has no such
notion.
A misuse of the term for those knowledgable in POSIX. I meant thread
specific storage cleanup handlers.
> Quote: "if a thread object is destroyed without join() having first
been
> called, the thread of execution continues until its initial function
> completes."
>
> Comment: This sounds scary. Another possibility is to have the
thread dtor
> call join(). If you think the existing semantics are best, it
would be nice
> to have a section in rationale.html explaining your reasoning.
This is the POSIX concept of a detached thread.
> Quote: "thread(const boost::function0<void> & threadfunc);"
>
> Comment: I would like to see a hyperlink here to a description of
the
> function0 template. I am not familiar with the function0 template,
so I have
> to ask: does the const prevent me from using a function object
whose state is
> intended to be modifiable by the newly-created thread?
Yes and no, with the emphasis on yes. This is an error in the
implementation.
> Quote: "Comparison Operators..."
>
> Comment: Gives requirements on *this (left-hand side of ==), but
not on rhs.
Hmm... I'll fix this.
> Quote: "static void sleep(const xtime & xt);"
>
> Comment: As previously mentioned, I have doubts about the use of
absolute time
> instead of duration. Again, how does sleep() know the appropriate
> clock_type/epoch to use?
>
>
> thread_group.html:
> ------------------
>
> Quote: "void remove_thread(thread* thrd);
> Effects: Removes *this's list of managed thread objects.
> Throws: ? if thrd is not it *this's list of managed thread
objects."
>
> Comment: What is the "?" for?
This was missed during documentation. I'll fix this.
> For effects, don't you mean "Removes thrd from
> *this's list of managed thread objects"?
Yep.
> call_once.html
> --------------
>
> Why does call_once() take a void (*func)() argument instead of a
function
> object?
Originally, for safety (constructing the function object could have
synchronization issues as well). I expect I'll change this, though.
It's been brought up before.
> thread_specific_ptr.html
> ------------------------
>
> Quote: "Note: There is an implementation specific limit to the
number of
> thread specific storage objects that can be created, and this limit
may be
> small."
>
> Comment: This note is not very useful without *some* way of knowing
what the
> limit may be. Perhaps there should be a const static value giving
the limit,
> which will vary from one platform to another. It would be nice to
know a
> lower bound on this limit (i.e., you're guaranteed at least 5
thread-specific
> pointers), and it would be nice to know the limit (or a reasonably
tight lower
> bound) on the most common platforms.
There's a limit on memory but we get along well with out a means to
tell how much memory is left for our use. There's also the issue
that on Win32 the number depends on the actual OS (which can be
worked around with runtime checks). Then there's the fact that we're
considering an implementation that limits this only by the amount of
available memory. This is something I've thought about but left out
in the initial release for all of these reasons.
> Quote: "Destructor... Does not destroy any data that may be stored
in any
> thread's thread specific storage. For this reason you should not
destroy a
> thread_specific_ptr object until you are certain there are no
threads running
> that have made use of its thread specific storage."
>
> Comment: This sounds unsafe and prone to resource leaks.
But it's also all that POSIX guarantees. In fact, it's recommended
you never call pthread_key_delete in portable programs by many books
for this very reason. This may all change at some point if we change
the implementation but for now this is the gaurantee.
> A rationale is
> needed here for why you consider this better than having
thread_specific_ptr
> be like auto_ptr or scoped_ptr (dtor automatically deletes pointer).
I don't consider it better, I just consider it the best gaurantee
given by the current implementations. Until a final decision is made
here I'm not sure that rationale will help anyone.
> xtime.html
> ----------
>
> Why not provide the obvious two-argument ctor for xtime? It would
still be
> compatible with the C version of xtime.
Because I find it highly unlikely that the C++ standard would do
this. I instead expect we'll either replace xtime entirely with a
C++ specific time concept or that a C++ specific time concept will
have a conversion (explicit or not) to xtime. In other words, I
don't expect a C++ programmer to use xtime with out some sort of OO
wrapper, if he'll use it at all.
Don't get too hung up on xtime. It's there solely as a placeholder
until Boost can decide upon a direction for time/duration handling
concepts.
> Quote: "xtime_get(struct xtime * xtp, int clock_type);"
>
> Comment: It's not crystal clear what clock_type is. I gather that
it's
> supposed to be one of the enums in the synopsis, but this isn't
explicitly
> stated anywhere.
You're correct. I need to work on this a bit.
> lock_error.html
> ---------------
>
> Quote: "class lock_error : public std::runtime_error"
>
> Comment: A lock_error is not a runtime error. It is an error which
it is
> reasonable to expect the programmer to avoid. Since it is a
programmer error,
> it should derive from std::logical_error.
I can buy that.
> I would recommend that exceptions indicating programmer errors
include the
> file name and line number where the error was detected, to aid in
debugging.
> Example:
>
> class lock_error : public std::logical_error
> {
> char const * file_;
> char const * line_;
> public:
> lock_error(char const * file, char const * line)
> : logic_error(string("Lock error at file: ") + file + ",
line: " + line)
> { }
> char const * file() { return file_; }
> char const * line() { return line_; }
> };
Storing the line as a seperate string is strange. If it were to be
stored sperately I'd store it as an integral type. However, this
whole concept is foreign to the design of the C++ standard and would
be considered an implemtation detail useful for debugging. So I'll
consider adding this info to the what() string, but I'm not sure it
should be documented as a requirement of the implementation.
> ...
> throw lock_error(__FILE__, __LINE__); // Perhaps define a macro
> ...
And this illustrates why C++ wasn't designed this way ;).
> config.html
> -----------
>
> You should make clear whether these macros are intended to be used
as
>
> #ifdef MACRO_NAME
>
> or
>
> #if MACRO_NAME
That would depend solely on the code using them, no? Seriously,
though, I think this is fairly obvious from the macro names, given
they are all BOOST_HAS_* variants.
Thanks for your comments. They are definately useful.
Bill Kempf
Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk