【问题标题】:Implementation of Lock using spinlocks in Anderson's Operating Systems Principles and Practice安德森操作系统原理与实践中使用自旋锁实现Lock
【发布时间】:2019-10-17 05:15:53
【问题描述】:

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

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();
     }
 }

其中 test-and-set 指令以原子方式从内存中读取一个值到寄存器,并将值 1 写入该内存位置。

现在他们通过以下方式实现锁:

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 中,并且 Lock 由其他线程 B 拥有。

我们获取 Lock 对象的自旋锁,将线程添加到等待列表中,并以 Lock 的自旋锁作为参数调用 scheduler.suspend。 在suspend方法中我们会获取调度器的自旋锁,假设它是空闲的,那么我们释放Lock的自旋锁,将正在运行的线程A的状态变为等待,从就绪队列中获取一个线程C然后执行一个上下文切换。

我不明白调度程序的自旋锁现在是如何释放的。在上下文切换中,堆栈指针更改为新线程 C 的堆栈指针,也交换了寄存器,特别是指令指针,因此 thread_switch 之后的语句直到旧线程 C 再次分配 CPU 时间才执行,对吗?所以我们假设调度器的自旋锁不是空闲的,而是被线程 A 持有。

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

【问题讨论】:

    标签: multithreading concurrency operating-system


    【解决方案1】:

    [警告:我没有读过安德森]。

    Thread_switch() 停止一个线程的执行,并恢复另一个线程的执行。这个另一个将立即在它自己的堆栈上返回到调用 到停止它的 thread_switch() 之后的指令。

    如果我们假设每个没有运行的线程因为调用suspend而没有运行,那么新唤醒的线程将释放被挂起的线程获得的调度程序自旋锁。如果我们不能做出这样的假设,但可以假设对 thread_switch 的每次调用都具有以下形式:

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

    那么确保你预想的场景不会发生就足够了—— schedulerSpinLock 将被 C 释放,因为 C 正在退出挂起,或者其他一些重复挂起模式的函数。

    这种设计的优点可能值得商榷。是否应该在一个线程中释放一个在另一个线程中分配的 spinlock() 可能是某些圈子中热议的话题。请放心,大多数内核都有一些杂耍纯语义来处理技术上暂停但仍在执行或技术上运行但仍暂停时的转换。

    【讨论】: