Lock Impl.

Implementing Locks

Optional readings for this topic from Operating Systems: Principles and Practice: Section 5.7.

How to implement locks and condition variables (inside the operating system)?

Uniprocessor solution: just disable interrupts.

class Lock {
    Lock() {}
    int locked = 0;
    ThreadQueue q;
};

void Lock::lock() {
    intrDisable();
    if (!locked) {
        locked = 1;
    } else {
        q.add(currentThread);
        blockThread();
    }
    intrEnable();
}

void Lock::unlock() {
    intrDisable();
    if (q.empty() {
        locked = 0;
    } else {
        unblockThread(q.remove());
    }
    intrEnable();
}

Implementing locks on a multi-core machine: turning off interrupts isn't enough.

  • Hardware provides some sort of atomic read-modify-write instruction, which can be used to build higher-level synchronization operations such as locks.
  • Example: exchange: atomically read memory value and replace it with a given value: returns old value.

Attempt #1:

class Lock {
    Lock() {}
    std::atomic<int> locked(0);
};

void Lock::lock() {
    while (locked.exchange(1)) {
        /* Do nothing */
    }
}

void Lock::unlock() {
    locked = 0;
}

Attempt #2:

class Lock {
    Lock() {}
    std::atomic<int> locked(0);
    ThreadQueue q;
};

void Lock::lock() {
    if (locked.exchange(1)) {
        q.add(currentThread);
        blockThread();
    }
}

void Lock::unlock() {
    if (q.empty() {
        locked = 0;
    } else {
        unblockThread(q.remove());
    }
}

Attempt #3:

class Lock {
    Lock() {}
    int locked = 0;
    ThreadQueue q;
    std::atomic<int> spinlock;
};

void Lock::lock() {
    while (spinlock.exchange(1)) {
        /* Do nothing */
    }
    if (!locked) {
        locked = 1;
        spinlock = 0;
    } else {
        q.add(currentThread);
        spinlock = 0;
        blockThread();
    }
}

void Lock::unlock() {
    while (spinlock.exchange(1)) {
        /* Do nothing */
    }
    if (q.empty() {
        locked = 0;
    } else {
        unblockThread(q.remove());
    }
    spinlock = 0;
}

Attempt #4:

class Lock {
    Lock() {}
    int locked = 0;
    ThreadQueue q;
    std::atomic<int> spinlock;
};

void Lock::lock() {
    while (spinLock.exchange(1)) {
        /* Do nothing */
    }
    if (!locked) {
        locked = 1;
        spinlock = 0;
    } else {
        q.add(currentThread);
        currentThread->state = BLOCKED;
        spinlock = 0;
        reschedule();
    }
}

void Lock::unlock() {
    while (spinlock.exchange(1)) {
        /* Do nothing */
    }
    if (q.empty() {
        locked = 0;
    } else {
        unblockThread(q.remove());
    }
    spinlock = 0;
}

Final solution:

class Lock {
    Lock() {}
    int locked = 0;
    ThreadQueue q;
    std::atomic<int> spinlock;
};

void Lock::lock() {
    intrDisable();
    while (spinlock.exchange(1)) {
        /* Do nothing */
    }
    if (!locked) {
        locked = 1;
        spinlock = 0;
    } else {
        q.add(currentThread);
        currentThread->state = BLOCKED;
        spinlock = 0;
        reschedule();
    }
    intrEnable();
}

void Lock::unlock() {
    intrDisable();
    while (spinlock.exchange(1)) {
        /* Do nothing */
    }
    if (q.empty() {
        locked = 0;
    } else {
        unblockThread(q.remove());
    }
    spinlock = 0;
    intrEnable();
}