mino
mino

Reputation: 7288

How does Monitor Synchronisation work?

So I'm reading about synchronisation and have come across Monitors, but can't seem to grasp how they work.

I see the general layout is something in the format of the following with what I can see as condition variables (and I'm not really sure what these do):

monitor {
    // shared variables
    procedure P1 (..) {..}
    ...
    procedure P2 (..) {..}
    initialisation code (..) {..}
}

However, as much as I bang my head against this, I can't see to really see how a monitor such as that described would allow only one process to access at a time.

Upvotes: 0

Views: 629

Answers (1)

Wandering Logic
Wandering Logic

Reputation: 3403

I like to think of a monitor as an object that has a private mutex, and where every procedure is protected by that mutex. So something like:

MyClass {
  // private shared variables
  ...
  mutex m;
  // public procedures
  Procedure P1(...) {
    acquire(m);
    ...
    release(m);
  }
  Procedure P2(...) {
    acquire(m);
    ...
    release(m);
  }
  initialisation code (...) { ... }
}

The mutex is guaranteeing that only one thread/process at a time can be manipulating the object, because every public Procedure is wrapped with mutex acquire/release.

Now what about those condition variables? Well, often you don't need them. But sometimes you have some condition where one thread needs to wait for another, that's where condition variables come in.

Suppose for example that you were implementing a concurrent queue object. You would have a push() procedure and a pop() procedure. But what should you do if the queue is empty?

It won't work to have separate empty() and pop() procedures, because of code like this:

Thread A                Thread B
if (not q.empty())
                        if (not q.empty())
                        then q.pop() // now q is empty!
then q.pop() // error!

So you would have to define an pop() procedure which returns a special value when the queue is empty, and then spin wait for the queue to become not empty. But spin waiting is incredibly inefficient.

So instead you use a condition variable. A condition variable has two methods: wait() and notify(). Wait() atomically gives up the mutex that you are holding, and then blocks the thread until some other thread calls notify(). So like this:

condition_variable cv;
Procedure int pop() {
  acquire(m);
  while (q.empty()) {
    wait(cv, m)  // atomically release m and wait for a notify() on cv.
    // when wait() returns it automatically reacquires mutex m for us.
  }
  rval = q.pop();
  release(m);
  return rval;
}
Procedure push(int x) {
  acquire(m);
  q.push(x);
  notify(cv);  // wake up everyone waiting on cv
  release(m);
}

Now you have a data structure/object where only one thread can be accessing the object at a time, and you can take care of situations where the operations between different threads need to happen in a particular order (like that at least one push() has to happen before each pop() can complete.)

Upvotes: 1

Related Questions