Boost logo

Boost :

From: Jesse Jones (jejones_at_[hidden])
Date: 2000-08-09 19:06:43


>I defined things the way I did on purpose, to generate debate about
>some issues.

Yep.

To me personally, it's very debatable whether or not a
>mutex should be sharable between processes. On the one hand it's an
>essential quality for a lot of concurrent programming chores. On the
>other hand, it adds a lot of overhead

But if we only support sharing via names and the name is omitted can't we
avoid the overhead?

>and may be very difficult
>(impossible?) to implement on some platforms today.

But this is true for all the threads classes. There will be platforms with
no support for threads, platforms that only support cooperative threads (eg
Mac OS 9), and perhaps platforms that don't support sharable mutexes. How
this is handled is a design choice we have to confront, but I'd be inclined
to support important features that are widely available even if it made
porting to certain platforms more difficult.

>> Windows for example has three primary synchronization mechanisms:
>>
>> 1) Critical Sections are recursive, but don't have timeouts, and
>can't be
>> accessed from a different process. However, in the absence of
>contention,
>> they're very fast to lock.
>
>The critical section does, however, have a try lock (at least on
>NT). The absence of a timed lock (or a more portable try lock) has
>resulted in a lot of code using the mutex in favor of the lighter
>weight and faster critical section.

Hmm, I'm not sure about this. The case I have in mind is library code that
doesn't use threads, but must be thread safe. For example, a singleton must
be protected so that it doesn't wind up being constructed twice. I've been
using critical sections for this, but if things somehow go haywire and the
app deadlocks a timeout might allow the user's work to be salvaged.

>For people who are absolutely
>dependent on getting the most performance possible then my interface
>may not be great on the Windows platform... but should a decision as
>important as this be made on such a corner case?

Well performance is important, epecially for such a low level construct.
It's true that most developers place too much emphasis on
micro-optimizations and on non-critical sections of code, but a solid
design that is as efficient as hand-crafted code seems like a worthy goal.
Especially if you consider the psychological aspects.

>> 2) Mutexes are like criticals sections but may have a timeout and
>can be
>> accessed from a different thread (eg by name).
>
>By name is actually the only way ;).

Child processes can also optionally share mutexes.

>> 3) Semaphores are like mutexes but also include a count.
>
>You missed Events.

I haven't used these much, but I guess you're right.

>> The classs below looks like a good start, but in most cases it
>provides
>> more functionality and overhead than I need.
>
>Overhead depends on implementation. As for functionality... you
>haven't said what functionality you think should be missing. Unless
>it's the timed lock you don't want... and I still think we need
>either timed or try locks.

I'm still thinking there should be a mutex that isn't shareable and doesn't
include a timeout. Something that can be as efficient as possible on most
platforms and used in speed critical bits of apps.

>> >class mutex
>> >{
>> >private:
>> > // Should copying be allowed?
>> > mutex(const mutex&);
>> > operator=(const mutex&);
>>
>> Maybe. I can imagine a thread safe wrapper around an STL container
>with the
>> STL container and a mutex as member data. It might be nice if the
>wrapper
>> could use the compiler provided copy ctor. But the new mutex
>shouldn't copy
>> the old mutex's locked state.
>
>Probably not. Another thought on copy-semantics is that it behaves
>like a smart_ptr copy. For some cases this seems appropriate, but in
>general I'd expect it to be the wrong choice. I mention it only to
>get people to thinking about possible semantics for a copy... IF a
>copy should even be allowed.

I don't see a compelling argument either way for copy vs. no-copy. I'd be
inclined to go with no-copy because work-arounds are easy to imagine and
the semantics in client code would be more explicit.

>> It might be worthwhile to allow for a mutex to start out locked.
>The idea
>> behind this is that other threads will be blocked allowing you to
>do some
>> additional initialization without worrying about race conditions.
>
>In general there shouldn't be race conditions here. If we allow
>named mutexes then the potential exists, and we should consider it.

I'm not sure how much of a problem this is in practice, but here's a
contrived example. Assume we have a Foo object with N lists. Each list has
it's own mutex and we want to do lazy creation of the mutex for some
ungodly reason. Something like this:

void Foo::Append1(int value)
{
   if (fAdd1Mutex == nil) {
      Mutex::lock lock(fCreateMutex);

      if (fAdd1Mutex == nil)
         fAdd1Mutex = new Mutex(kLocked);

   } else
      fAdd1Mutex->Lock();

   mList1.push_back(value);

   fAdd1Mutex->Unlock();
}

Of course to be safe we'd have to wrap a try block around push_back, but
this is the basic idea. I'm having trouble coming up with a plausible
example of where this would be useful, but I thought it was at least worth
bringing up.

>I left it out initially because we've removed all ability to unlock
>the mutex from the public interface here.

Yup.

>> > lock(mutex& mx);
>> > // Throws an exception if timed out
>>
>> What exception?
>
>One we'd have to define, of course. That shouldn't cloud the
>discussion here, so I left the specifics out.
>

You could say runtime_error or something descending from runtime_error.

>> > // Attempts to lock the mutex. If the mutex can not be
>> > // locked within the given time frame the result is false;
>> > // otherwise it's true.
>> > bool do_lock(int timeout);
>>
>> This should just throw. That way implementors have at least a shot
>at using
>> any error codes returned by the OS.
>
>This is an internal method. In my original design the c-tor that
>called it did the throwing. With the revised sentry it may be
>appropriate to throw here instead, but I'm not buying that just yet.
>Exceptions are a pretty heavy handed form of error handling.

Exceptions are the way to handle errors in C++. If timeouts are an unusual
case and client code typically can't do it's job without acquiring the
mutex then exceptions are appropiate.

>The
>code required to use a timed lock would become burdensome if a time
>out threw an exception. The only (typically) valid reason for this
>call to fail would be because of timeout, so we shouldn't need access
>to any error codes returned by the OS.

This is a pet peeve of mine: interfaces that throw away system error codes
by returning just a success or fail flag. You may well be right that error
codes other than timeout are highly unusual, but forbidding implementors to
return the error code in an exception sits poorly with me.

  -- Jesse


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