Lecture 14

SFR1811·2022년 2월 5일
0

CS343-Parallel Programming

목록 보기
11/24

6.1 Types of locks

why do we have different types of locks?

Spinning Locks

  • it is smallest lock that we can build
  • possibly a subject of starvation but not probablisticly not a problem because portion with spin lock will be so small.
  • if you are working on single core CPU, all the time you are spin waiting in front of the lock is a waste of time.

Blocking Locks

  • you test once with spin lock
  • if it is open, you acquire it!
  • else, go to sleep.
  • but for this to work, you have to be cooperative
  • the one that is releasing the lock must wake someone in asleep.

Blocking locks reduce busy waiting by having releasing task do additional work: coopertion

6.3.1 Mutex Lock

divided into two kinds

  • single aquisition: if you have the lock, you can't acquire it again
  • multiple acquisition: the owner of lock can acquire lock again - called an owner lock
    • you can acquired multiple times and release all at once, or
    • release as much time as you have acquired
      • but for this to work, you need a counter in the lock

Implementation - two for one lock!

class MutexLock{
  bool avail;
  Task * owner
  queue <Task> blocked;
  SpinLock lock;
 public:
  MutexLock() : avail(true), owner(nullptr){}
  void acquire(){
    lock.acquire(); // barging. potential starvation
    while(!avail && owner!=currThread()){ // busy wait
      // add self to lock's blocked list
      // <<< lock.release();  releasing here does not work
      // because at this right moment, there may be release called
      // and tries to wake you, but you haven't gone sleep yet
      // so it passes by and then you go to sleep.
      // and no one wakes you up ever again.
      yieldNoSchedule(lock); // do not reschedule to ready queue
      // lock is passed to yieldNoSchedule and the KERNAL releases
      // the lock for you after you go to sleep.
      // normal yield puts itself in to a ready queue
      lock.acqcuire(); // reacquire spinlock
      // ^^ this lock.acquire can compete with the first acquire
      //    which caues a barging.
    }
    avail = false;
    owner = currThread();
    lock.release();
  }
  void release(){
    lock.acquire();
    if(owner!=currThread()) ... // ERROR CHECK
    owner = nullptr
    if(!blocked.empth()){
      // remove task from blocked list and make ready
    }
    avail = true;   // reset
    lock.release(); // RACE
  }
}

Barging avoidance.
you allow ppl to barge. but, as soon as someone barges, you redirect him to a chair!
if there is a person in a blocked list, no new commers can go passed the door!

void acquire(){
  lock.acquire();
  if(!avail && owner != currThread()){ // avoid barging
    // add self to lock's blocked list
    yieldNoSchedule(lock);
    lock.acquire();
  }
  avail = false;
  owner = currThread();
  lock.release();
}
void release(){
  lock.acquire();
  owner = nullptr;
  if(!blocked.empty()){
    // remove task from blocked list and make ready
  }else{
    avail = true;
  }
  lock.release();
}

Barging prevension

void acquire(){
  lock.acquire();
  if (!avail && owner != currThread()){
    // add self to lock's blocked list
    yieldNoSchedule(lock);
    // DO NOT REACQUIRE LOCK!!!
  }
  avail = false;
  owner = currThread();
  lock.release();
}
void release(){
  lock.acquire();
  owner = nullptr;
  if(!blocked.empty()){
    //remove task from blocked list and make ready
    // DO NOT RELEASE LOCK
  }else{
    avail = true;
    lock.release();
  }
}

here, in some case, release() acquires the spin lock and acquire() releases the spin lock.

Is there a reason you would perfer barging avoidence rather than barging prevension?
A: If you are not in a position that you can seperate conceptual brackets of lock acquire and lock release - baton passing - you cannot use barging prevension.


6.3.1.2 uOwnerLock

Blocking multi-acquisition mutex-lockin uC++ requiring multiple releases.

class uOwnerLock{
 public:
  uOwnerLock();
  uBaseTask * owner();
  unsigned int times();
  void acquire();
  bool tryacquire();
  void release();
}

owner() returns the address of task which can be compared with uThisTsk().

6.3.1.3 Mutex-Lock Release-Pattern

uOwnerLock lock;
lock.acquire();
try{
  ...
}_Finally{
  lock.release();
}

** this may not work depending on what lock you are using because you may have to release it multiple times.

This prevents potential deadlock caused by exceptions.

in regular c++, use RAII - Resource Acquisition Is Initialization.

class RAII{
  uOwnerLock &lock;
 public:
  RAII(uOwnerLock &lock): lock(lock) {lock.acquire();}
  ~RAII(){lock.release();}
};

uOwnerLock lock;
{
  RAII raii(lock);
  ...
}

RAII object will call its destructor upon exception, or exiting scope.

6.3.1.4 Stream Locks

IO stream is a CS.
uC++ has a built in mutex lock for IO based on uOwnerLock

task1: osacquire(cout) << "abc" << "def" << endl;
task2: osacquire(cout) << "uvw" << "xyz" << endl;

the ending semicolon releases the object.

acquire IO stream for block of instructions

{
  osacquire OA(cout);
  cout << ... << endl;
  cout << ... << endl;
  // you can acquire it multiple times because it has ownerships
  // it wouldn't be any different from just using cout in this context
  osacquire(cout) << ... << endl;
} // OA is destructed here, so lock is released.
profile
3B CS

0개의 댓글