Can we get some help from the compiler for concurrency errors?
Declare which variables are to be shared:
The program will not even compile if you lock and release around critical section!
This cannot do simultanious read :/
Does this help us with deadlock?
Not really, but its still better than nothing!
Conditional critical regions can only be entered if it acquired the resource, and meets the condition in AWAIT clause. if either of them was not met, you wait.
REGION v DO
AWAIT conditional-expression
. . .
END REGION
if the condition is false, the region lock is released and entry is started again - busy waiting
ex)
VAR Q : SHARED QUEUE<INT,10>
REGION Q DO
AWAIT NOT EMPTY( Q ) buffer not empty
take an item from the front of the queue
END REGION
_Monitor name {
shared data
members that see and modify the data
};
A monitor is an abstract data type that combines shared data with serialization of its modification.
if you call the member routine - interface members - of the monitor, it automatically aquires a lock.
-> A mutex member is one that does not execute if there is another active mutex member.
-> So only one routine can be run at a time
Basic monitor
class Mon{
MutexLock mlock;
int v;
public:
int x(...){ // mutex member
mlock.acquire();
... // int temp = v;
mlock.release();
return v; // return temp;
}
}
_Monitor provides much more powerful mutex functionalities which allows user to think in higher level.
ex)
_Monitor AtomicCounter{
int counter;
public:
AtomicCounter(int init = 0) : Ccounter( init ) {}
// mutex members
int inc() {counter += 1; return counter;}
int dec() {counter -= 1; return counter;}
}
AtomicCounter a,b,c;
// accessed by multiple threads
... a.inc(); ...
... b.dec(); ...
... c.inc(); ...
It may want to schedule tasks other than the order in which they arrive.
Two techniques:
ex) Bounded Buffer
_Monitor BoundedBuffer {
int front = 0, back = 0, count = 0;
int elements[20];
public:
_Nomutex int query() const { return count; }
[ _Mutex] void insert( int elem );
[ _Mutex] int remove();
};
void BoundedBuffer::insert( int elem ) {
if ( count == 20 ) _Accept( remove );
elements[back] = elem;
back = ( back + 1 ) % 20;
count += 1;
}
int BoundedBuffer::remove() {
if ( count == 0 ) _Accept( insert );
int elem = elements[front];
front = ( front + 1 ) % 20;
count -= 1;
return elem;
}
void BoundedBuffer::insert( int elem ) {
if ( count == 20 ) _Accept( remove );
_Accept does the following
When BoundedBuffer::remove returns, _Monitor checks if any thread is waiting for remove to be called and wakes up.
_Accept takes multiple parameters.
_Accept( remove, somethingElse );
This means remove or somethingElse is accepted to continue progress.
The Accepter chair is actually a stack because
this function does not really answer anything because the moment you get a return from query(), the count may be incremented or decremented.
public:
_Nomutex int query() const { return count; }
You may want this function for analysis purposes here.
External scheduling is simple because there is no signaling.
_Monitor has internal condition lock. uCondLock would not work in _Monitor because it is ran by different lock.
A task waits by placing itself on a condition:
x.wait(); // wait(mutex, condition)
//////////
x.signal();
if I signal x, x cannot wake up NOW because I am still in the monitor routine. x can only wake up once I leave monitor, so I need to place x somewhere else until I leave.