Boost logo

Boost :

From: Kevin S. Van Horn (kevin.vanhorn_at_[hidden])
Date: 2001-11-19 10:18:17


William Kempf wrote:

>> Also, the documentation should make clear that there are two kinds of
>> blocking in a wait() operation: [...]
>
>I'm not sure how this knowledge (which should be obvious when
>thinking about it) will be useful.

It's something you need to keep in mind when reasoning about fairness /
starvation. The two-step nature of a wait -- and the fact that another thread
can grab the mutex and falsify the predicate before the notified thread can
acquire the mutex -- makes it easy to starve a thread waiting for a predicate
to become true. Contrast this with the original semantics for monitors, for
which a process awakened by the equivalent of notify_one() is guaranteed that
the desired predicate still holds (assuming it held at the moment of
notification).

>> Handling of programmer errors
>> -----------------------------
>
>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.

The point is that if the documentation *guarantees* that an exception will be
thrown, the implementation no longer has the freedom to do an assert on debug
builds if desired. This is why as a general rule I oppose any *guarantee*
that an exception will be thrown for programmer errors.

>> Comment: The programmer cannot do anything about priority inversion; that
>> has to be addressed at the OS level.
>
> This isn't true.

OK, you're right.

> 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. [...]
>
> I'm not sure how to reword the clause to be more meaningful, however.

What do you mean by "provide the functionality, but make it's use prohibitive
for general use"? Does this mean that the functionality may be used
internally within Boost libraries, but no external interface is provided?

>> Comment: This [simulating semaphores with mutexes and conditions] is not so
>> easy to do correctly if you wish to avoid starvation!
>
> Care to elaborate?

The obvious way of simulating semaphores with mutexes and conditions -- in
fact, the implementation you used to have -- provides a weak and unfair
semaphore that is prone to starvation, as I pointed out in an earlier message
on this list.

>> Quote: "Thread State." [Followed by definitions of "Ready" and "Running".
>>
>> Comment: You are getting into scheduler implementation details [...]
>
>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.

The notion of ready vs. running is a scheduler implementation detail. From
the standpoint of externally visible behavior, there is no need to talk about
ready vs. running. Instead, you can just talk about "blocked"
vs. "non-blocked", and note that there are no guarantees as to
the relative rates at which different threads progress through their programs,
nor is there any guarantee that these execution rates are constant (threads
may speed up or slow down.)

>> Other comments:
>> - I'm uncomfortable with the use of absolute
> Absolute times are basically required for condition variables

OK, you convinced me. It might be nice to have convenience functions that
allow you to simply specify a relative time, though.

>> 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.

Ah, I think I see the cause of the confusion now. [Pause while I leaf through
my dictionary to verify my understanding of "ensures"...] Here is the
definition of "ensure":

   to make sure, certain, or safe : guarantee

I believe the word you want is "verify":

  to confirm or substantiate; to establish the truth, accuracy, or reality of

The wait function doesn't *ensure* that the mutex is locked (i.e., lock it if
it isn't already locked); rather, it *verifies* that the mutex is locked
(checks to make sure).

>> Quote: "void wait(ScopedLock & lock); ... All effects occur in an atomic
>> fashion."
>>
>> Comment: This is not and cannot be true. [...]
>
> Technically accurate, but I'm not sure the distinction is useful.

The distinction is necessary because you do not want to give the impression
that the notified thread immediately grabs the mutex before any interfering
thread can make the predicate false again.

>> 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. [...]
>
>This is the POSIX concept of a detached thread.

Perhaps my original comment was a bit off the mark, but the issue I am most
concerned about is making sure that any objects on which the thread depends
(e.g., data members of the function object whose operator() is run by the
thread) stick around for the lifetime of the thread.

>> 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. [...]
>
>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.

Ah, but that limit on memory is generally not small. Furthermore, we know
what to do if the memory limit is too small for our application -- install
more memory on the machine, or run it on a bigger machine. There is no such
remedy if I have a program that requires more thread-specific-storage objects
than a particular platform can provide. Even the drastic remedy of moving to
a different platform is not available, because I have no way of knowing
whether even that will improve or worsen matters.

As the specification stands, I can't write portable code that has more than
one thread-specific-storage object, because it doesn't even guarantee that I
can have two different TSS objects.

[continuation]
> There's also the issue that on Win32 the number depends on the actual OS

Sure, but you can at least take the minimum of those numbers and provide that
as a guarantee.

[continuation]
> Then there's the fact that we're considering an implementation that limits
> this only by the amount of available memory.

I don't see the problem. It's always permissible to guarantee *more* at a
later time than you were previously willing to guarantee. I don't see why you
can't just determine some number N of TSS objects that you know you can support
on all platforms for the current implementation -- and feel comfortable that
will be able to support on any other likely platforms -- and say, "It is
guaranteed that you can have at least N TSS objects." If at some later time
you find that you can actually guarantee M > N, you just replace the old
guarantee with a stronger guarantee.

> Quote: "Destructor... Does not destroy any data that may be stored in any
> thread's thread specific storage. [...]
> Comment: This sounds unsafe and prone to resource leaks.
>
> But it's also all that POSIX guarantees.

But POSIX is a C API, not a C++ API. Is there any implementation difficulty
with having the destructor for a TSS object destroy the thread-specific data
for every thread? What about having the TSS object keep track of all the
threads that have associated a value with the TSS object?

> I would recommend that exceptions indicating programmer errors include the
> file name and line number where the error was detected, to aid in
> debugging. [...]
>
> Storing the line as a seperate string is strange.

My mistake; I was thinking that the __LINE__ macro evaluated to a string
literal.

[continuation]
> 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.

I would be happy with that.

-----

BTW, I will continue the discussion about fairness and weak vs. strong mutexes
in another message; right now I'm still thinking things over.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk