使用安德森操作系统原理和实践中的自旋锁实现锁

时间:2019-05-31 14:33:56

标签: multithreading concurrency operating-system

在多处理器系统上给出以下伪代码:

class SpinLock {
   private:
     int value = 0; // 0 = FREE; 1 = BUSY
 
   public:
     void acquire() {
         while (test_and_set(&value)) // while BUSY
             ; // spin
     }
 
     void release() {
         value = 0;
         memory_barrier();
     }
 }

其中,测试设置指令从内存中原子读取一个值到寄存器,并将值1写入该内存位置。

现在,它们通过以下方式实现Locks:

class Lock {
    private:
      int value = FREE;
      SpinLock spinLock;
      Queue waiting;
    public:
      void acquire();
      void release();
 }
 
 Lock::acquire() {
     spinLock.acquire();
     if (value != FREE) {
         waiting.add(runningThread);
         scheduler.suspend(&spinLock);
        // scheduler releases spinLock
     } else {
         value = BUSY;
         spinLock.release();
     }
 }
 
 void Lock::release() {
     TCB *next;
 
     spinLock.acquire();
     if (waiting.notEmpty()) {
         next = waiting.remove();
         scheduler.makeReady(next);
     } else {
         value = FREE;
     }
     spinLock.release();
 }
 class Scheduler {
   private:
     Queue readyList;
     SpinLock schedulerSpinLock;
   public:
     void suspend(SpinLock *lock);”
     void makeReady(Thread *thread);
 }
 
 void
 Scheduler::suspend(SpinLock *lock) {
     TCB *chosenTCB;
 
     disableInterrupts();
     schedulerSpinLock.acquire();
     lock->release();
     runningThread->state = WAITING;
     chosenTCB = readyList.getNextThread();
     thread_switch(runningThread,
                   chosenTCB);
     runningThread->state = RUNNING;
     schedulerSpinLock.release();
     enableInterrupts();
 }
 
 void
 Scheduler::makeReady(TCB *thread) {
     disableInterrupts();
     schedulerSpinLock.acquire();
     readyList.add(thread);
     thread->state = READY;
     schedulerSpinLock.release();
     enableInterrupts();
 }

我不知道这是如何工作的。假设当我们调用acquire()时,我们位于线程A中,而锁由其他线程B拥有。

我们获取Lock对象的自旋锁,将线程添加到等待列表中,并以Lock的自旋锁作为参数调用scheduler.suspend。 在suspend方法中,我们将获取调度程序的自旋锁,假设这是免费的,然后释放Lock的自旋锁,将运行线程A的状态更改为waiting,从就绪队列中获取线程C,然后执行上下文切换。

我不知道现在如何释放调度程序的自旋锁。在上下文切换中,将堆栈指针更改为新线程C的堆栈指针,还交换了寄存器,特别是指令指针,因此在重新分配旧线程C的CPU时间之前,不会执行thread_switch之后的语句,对吗?因此,假设调度程序的自旋锁不是免费的,而是由线程A保持的。

假定线程B释放了它的锁。它获取了Lock的自旋锁,该锁是免费的,并且等待队列中至少有一个线程,即A。假设选择A作为下一个线程,因此我们以线程A作为参数调用scheduler.makeReady。但是现在在makeReady内部有一个调用来获取调度程序的自旋锁,该调用未释放,因为在执行线程A时,我们在Scheduler :: suspend()内部调用schedulerSpinlock.release()之前执行了上下文切换。如果我们不能再次运行它,线程A立即释放调度程序的自旋锁吗?

1 个答案:

答案 0 :(得分:1)

[caveat:我还没看过安德森]。

Thread_switch()停止一个线程的执行,并继续执行另一个线程。在调用使其停止的thread_switch()调用之后,另一个将立即在其自己的堆栈上返回指令。

如果我们假设每个未运行的线程由于调用suspend而变为未运行,那么刚唤醒的线程将释放由挂起自身的线程获取的调度程序自旋锁。如果我们不能做这个假设,但是可以假设对thread_switch的每次调用都采用以下形式:

 schedulerSpinLock.acquire();
 ...
 thread_switch(cur, new);
 ...
 schedulerSpinLock.release();

那么就足以确保不会发生您所设想的场景-C会因为C正在退出挂起或其他某些重复挂起模式的功能而将schedulerSpinLock释放。

这种设计的优点可能是有争议的。是否应该在分配给另一个线程的一个线程中释放spinlock()可能是某些领域的热门话题。放心,大多数内核都有一些杂凑的纯语义,可以在技术暂停但仍在执行或技术运行但仍暂停的情况下处理过渡。