Boost logo

Boost :

From: Eric D Crahen (crahen_at_[hidden])
Date: 2002-05-19 06:34:22


> I guess you've never seen one. There is NO hazard of electrocution
> while attempting to put your lock on.. you're not in the hazard area.
> And it doesn't matter who locks it "first" you both then get to go do
> your work.

In the real world, there isn't because people will cooperate. But
when implementing this in code this is a problem, unless you have
serialized access to the resource itself. This is what could happen
if the resource is not serialized.

  T1: is power on? No? keep going.
  T2: is power on? No? keep going.

  T1: <delayed by scheduler> (almost in the dangerzone)
  T2: lock added, do work
  T2: remove lock (left danger zone)

  T3: turn on power (T1 hasn't finished adding its lock,
      and its already past the point where it would wait for the power
      to turn off.)

  T1: <scheduler resumes> bad things happen, T1 is working
      with the power on.

If T1 was able to resume before T2 left the work area then T3 wouldn't
hurt anything by turning the power on. Its a race condition. Even with
only two thread, one going in to do work and one waiting for the power
its possible this condition exists. If you place all the serialization
controls within the resource, and you choose the correct kinds of
controls, you can eliminate this problem.

(I haven't seen one in the real world BTW, so I'm working from my
understanding of the scenario you described)

> I'm in favor of semaphores, but my review of the message history shows
> that some felt they were "too dangerous" (a shame, IMO).

This would be a good use for a read-write lock actually now that I think
of it. It just struck me as I was writing part of this message, so
I'm mentioning it here. The reason people feel a semaphore alone is too
dangerous is because it is too dangerous. You need to model a safe count
for the number of padlocks on the resource. If it was done with just a
semaphore, there would be no way to make sure that no one is in the
danger zone when the power gets turned on.

class Resource {

  Semaphore sema;

  void addPadlock() { sema.increment(); }

  void delPadlock() { sema.decrement(); }

  void powerOn() {

    if(sema.count() > 0)
      throw;

    // No synchronization here - count may change
    // before the power goes on.

    // turn the power on ...

  }

};

You need to serialize access to the resource to implement this safely. If
you wanted to add blocking for threads trying to get into the danger zone
and threads coming trying to turn the power one (so people wait until its
sage to do either thing) you'd need a pair of semaphores to do that,
minimally - but you still need serial access to the resource for saftey,
so using condition variables for that would be a better choice.

If you have a way to safely do this semaphores, please share it.

> mayhaps, yet we transfer "ownership" of other things (logically, as
> in "who calls delete on this pointer?", and automagically as int
> auto_ptr<>). I submit that transferring "ownership" of a resource is no
> different.

I might have been using the ownership term vaguely, I take an OO approach
to these things so to me, many things are kinds of locks. Not all of them
have owners, they hold locks (for example, who "owns" a read lock on a
read-write lock? more than one thread can) and some locks can be held
by several threads at the same time. So its sort of misleading to talk
about ownership if it isn't really exclusive (for me anyway). I'll be more
careful this time in order to be clearer.

What is the advantage to externalizing the synchronization controls for
some resource? In the example I showed, its very easy to see how and
when things are locked, the code using the resource only needs to know
how to use the resource - its the resource's responsiblity to make sure
the access is safe. There is only 1 place in the code where you'd need
to check to make sure the locks are correctly locked (in the resources
code). If you model this with auto_ptr type semantics, you will succeed
in distributing the synchronization logic throughout every piece of code
using the resource, its more complicated (you have to following the
assignments of locked_ptr's to see where the "ownership" currently is). Is
there a benefit to that?

I don't understand why a client of some piece of code should ever
explicitly do anything to control serialization of something it uses. If
there is a resource that requires synchronization, the resource itself
should be responsible for that, not the client. Why burden the client?
A client may not always know when a resource needs to be synchronized
unless you want to expose alot of the details about that resource to
a client (which makes a good OO design difficult).

> I read all the words, and I don't understand what you're trying to say.

What I'm saying is basically two things, one is that a lock has to lock
something. In the example I showed, the lock was for the resource to
secure access to it. If you are saying that you want to model this by
sharing many locks owned by many workers in someway in order to synchronize
access to one resource, then what is each lock really for? What makes that
choice more attractive than use just a single lock? I'm not sure if this is
what you meant in your original post or not - but it seemed this was what you
wanted to do from your explination.

The second thing is that if your design your code so that a lock for a
resource is never exposed in anyway to any client of the resource,
there is no need to burden the client with using a special pointer to that
object that will automaticaly transfer "ownership". You can implicitly
transfer the "ownership" of the lock by calling a function on the shared resource.

class Resource {

  Mutex lock;

  void doSomething() {

    Guard<Mutex> g(lock);
    // "ownership" implicity transfered to the caller

  }

};

To me anyway, this is a tighter design, the lock never has to be
exposed to anything outside the bank. You can still use this kind of
approach to make sure a lock is held for a longer duration of time without
the client of the resource ever needing to know anything directly about
_how_ serialization is being provided.

class Bank {

   Mutex lock;

   void commit(Transaction& t) {

     Guard<Mutex> g(lock);
     // "ownership" transfered to the execute() function
     t.execute();

   }

};

You can transfer "ownership" of a lock very easily this way and you
end up with something that is more flexible and has fewer dependancies
(the transaction doesn't need to depend on the Bank, and the Bank doesn't
need to pray the Transaction does the right thing) I've always have been
able to do this and if I have ever found that I'm in a situation where I
suddenly need to know about another objects lock then I take it as
indication that my design is somewhat convoluted and has a problem. not
the synchornization model. Just break the problem down differently.

- Eric
http://www.cse.buffalo.edu/~crahen


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