Monitor

Internal Scheduling

Internal scheduling schedules tasks inside a monitor/task using condition variables, uC++ uCondition. A task waits on a condition to block (atomically releasing the monitor lock); another task signals to wake the front of the queue, or signalBlock to wake and block itself. Unlike [[notes/External Scheduling|external _Accept]], internal scheduling can condition on any state, including member arguments.

Why both signal and signalBlock?

Because the signaller might still hold live state in the monitor. signal (uC++ default) makes the signalled task ready but the signaller continues; the signalled task runs only after the signaller exits or waits. signalBlock (Hoare-style) immediately swaps: signaller blocks, signalled runs. Use signalBlock when the signaller is done and wants the signalled task to see state right now.

Interface (uCondition)

uCondition c;            // external synchronization-lock = queue of waiters
c.wait();                // atomically release monitor lock; block; re-acquire on wake
c.signal();              // wake front of queue; signalled runs after signaller exits/waits
c.signalBlock();         // wake front; signaller blocks, signalled runs now
bool c.empty();          // true if no waiters
int  c.front();          // integer stored with the front waiter (for priority)
c.wait( N );             // wait, storing N with this waiter

Bounded buffer (internal)

_Monitor BoundedBuffer {
    uCondition full, empty;
    int front = 0, back = 0, count = 0;
    int elements[20];
public:
    _Nomutex int query() const { return count; }
    void insert( int elem ) {
        if ( count == 20 ) empty.wait();        // wait for a free slot
        elements[back] = elem; back = (back + 1) % 20; count += 1;
        full.signal();                          // wake a waiting consumer
    }
    int remove() {
        if ( count == 0 ) full.wait();          // wait for an item
        int elem = elements[front]; front = (front + 1) % 20; count -= 1;
        empty.signal();                         // wake a waiting producer
        return elem;
    }
};

Rules

  • Wait releases the monitor lock atomically with blocking.
  • Signal on empty queue is lost (unlike V on a semaphore, which is remembered).
  • No barging in uC++, the signalled task runs before any newly-arrived caller enters the monitor (priority blocking/nonblocking; see Monitor Types).
  • Signaller does not block on signal, signalled waits until signaller exits or waits. Use signalBlock to swap immediately.

Shadow queue (CS343 Solution 4)

uCondition stores an integer with each waiter (wait(N) + front()), enabling temporal-order reader-writer: record READER/WRITER at arrival; release cascade checks the front’s kind.

Dating Service, why internal beats external (Buhr §8.4.2)

Canonical case where external scheduling can’t reach: matches depend on member argument (ccode). _Accept can pick a member but can’t inspect its arguments, so you need per-code condition queues:

_Monitor DatingService {
    enum { CCodes = 20 };
    uCondition girls[CCodes], boys[CCodes], exchange;
    int girlPhoneNo, boyPhoneNo;
public:
    int girl( int phoneNo, int ccode ) {
        if ( boys[ccode].empty() ) {            // no compatible boy ?
            girls[ccode].wait();                // wait for a boy
            girlPhoneNo = phoneNo;              // expose phone to the signaller
            exchange.signal();                  // wake boy from chair
        } else {
            girlPhoneNo = phoneNo;
            boys[ccode].signal();               // wake the waiting boy
            exchange.wait();                    // sit in chair for boy's number
        }
        return boyPhoneNo;
    }
    int boy( int phoneNo, int ccode ) { /* symmetric */ }
};

Key trick: the signaller deposits its phone number in the shared variable before signal, and the signalled task reads it after being woken. The exchange condition is a single-slot “chair” for the rendezvous.