Lecture 20

SFR1811·2022년 3월 2일
0

CS343-Parallel Programming

목록 보기
17/24

Can we get some help from the compiler for concurrency errors?

8.1 Critical Regions

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!

8.2 Conditional Critical Regions

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

8.3 Monitor

_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(); ... 

8.4 Scheduling (Synchronization)

It may want to schedule tasks other than the order in which they arrive.

Two techniques:

  • external
    - schedules tasks outside of the modnitor.
    - accomplished with accept statement.
  • internal
    - schedules tasks inside of the modnitor.
    - accomplished with condition variables with signal & wait.

8.4.1 External Scheduling

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

  1. closes insert function - no tasks can have progress with insert anymore
  2. release mutax lock
  3. blocks itself from progress until BoundedBuffer::remove is called
  4. skips the calling queues of all others, and bring BoundedBuffer::remove to the chair and run it.
    so there is some lock passing going on.

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

  • the routine in _Accept may also call _Accept

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.


8.4.2 Internal Scheduling

_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.

General Model of uC++ _Monitor

  • mutex queues: it is to avoid O(n) search from _Accept. if it didn't have mutex queue, it had to iterate entry queue.
profile
3B CS

0개의 댓글