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